Переглянути джерело

feat: use getSynchronizationSources on the receiver for remote audio levels (#1245)

* feat: use getSynchronizationSources on the receiver for remote audio levels
Use getSynchronizationSources if it is supported, fallback to using getStats otherwise.
* feat/ref: Use the local audio levels from LocalStatsCollector
When using getSynchronizationSources, use the audio levels from LocalStatsCollector for NoAudioSignalDetection.js
Remove obsolete code - TalkMutedDetection feature using audio levels is not used anymore
dev1
Jaya Allamsetty 4 роки тому
джерело
коміт
b4a4ad2a00
Аккаунт користувача з таким Email не знайдено

+ 1
- 6
JitsiConference.js Переглянути файл

@@ -15,7 +15,6 @@ import * as JitsiTrackEvents from './JitsiTrackEvents';
15 15
 import authenticateAndUpgradeRole from './authenticateAndUpgradeRole';
16 16
 import P2PDominantSpeakerDetection from './modules/detection/P2PDominantSpeakerDetection';
17 17
 import RTC from './modules/RTC/RTC';
18
-import TalkMutedDetection from './modules/detection/TalkMutedDetection';
19 18
 import VADTalkMutedDetection from './modules/detection/VADTalkMutedDetection';
20 19
 import VADNoiseDetection from './modules/detection/VADNoiseDetection';
21 20
 import VADAudioAnalyser from './modules/detection/VADAudioAnalyser';
@@ -410,12 +409,8 @@ JitsiConference.prototype._init = function(options = {}) {
410 409
                 this.eventEmitter.emit(JitsiConferenceEvents.TALK_WHILE_MUTED));
411 410
 
412 411
             this._audioAnalyser.addVADDetectionService(vadTalkMutedDetection);
413
-
414
-
415 412
         } else {
416
-            logger.info('Using audio level based detection for generating talk while muted events');
417
-            this._talkWhileMutedDetection = new TalkMutedDetection(
418
-                this, () => this.eventEmitter.emit(JitsiConferenceEvents.TALK_WHILE_MUTED));
413
+            logger.warn('No VAD Processor was provided. Talk while muted detection service was not initialized!');
419 414
         }
420 415
     }
421 416
 

+ 6
- 0
JitsiTrackEvents.js Переглянути файл

@@ -37,3 +37,9 @@ export const TRACK_VIDEOTYPE_CHANGED = 'track.videoTypeChanged';
37 37
  * to receive data (i.e. the stream is not stopped).
38 38
  */
39 39
 export const NO_DATA_FROM_SOURCE = 'track.no_data_from_source';
40
+
41
+/**
42
+ * Indicates that the local audio track is not receiving any audio input from
43
+ * the microphone that is currently selected.
44
+ */
45
+export const NO_AUDIO_INPUT = 'track.no_audio_input';

+ 10
- 0
modules/RTC/JitsiTrack.js Переглянути файл

@@ -428,6 +428,16 @@ export default class JitsiTrack extends EventEmitter {
428 428
                 JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,
429 429
                 audioLevel,
430 430
                 tpc);
431
+
432
+        // LocalStatsCollector reports a value of 0.008 for muted mics
433
+        // and a value of 0 when there is no audio input.
434
+        } else if (this.audioLevel === 0
435
+            && audioLevel === 0
436
+            && this.isLocal()
437
+            && !this.isWebRTCTrackMuted()) {
438
+            this.emit(
439
+                JitsiTrackEvents.NO_AUDIO_INPUT,
440
+                audioLevel);
431 441
         }
432 442
     }
433 443
 

+ 27
- 0
modules/RTC/TraceablePeerConnection.js Переглянути файл

@@ -487,6 +487,33 @@ TraceablePeerConnection.prototype._peerMutedChanged = function(
487 487
     }
488 488
 };
489 489
 
490
+/**
491
+ * Obtains audio levels of the remote audio tracks by getting the source
492
+ * information on the RTCRtpReceivers. The information relevant to the ssrc
493
+ * is updated each time a RTP packet constaining the ssrc is received.
494
+ * @returns {Object} containing ssrc and audio level information as a
495
+ * key-value pair.
496
+ */
497
+TraceablePeerConnection.prototype.getAudioLevels = function() {
498
+    const audioLevels = {};
499
+    const audioReceivers = this.peerconnection.getReceivers()
500
+        .filter(receiver => receiver.track && receiver.track.kind === MediaType.AUDIO);
501
+
502
+    audioReceivers.forEach(remote => {
503
+        const ssrc = remote.getSynchronizationSources();
504
+
505
+        if (ssrc && ssrc.length) {
506
+            // As per spec, this audiolevel is a value between 0..1 (linear), where 1.0
507
+            // represents 0 dBov, 0 represents silence, and 0.5 represents approximately
508
+            // 6 dBSPL change in the sound pressure level from 0 dBov.
509
+            // https://www.w3.org/TR/webrtc/#dom-rtcrtpcontributingsource-audiolevel
510
+            audioLevels[ssrc[0].source] = ssrc[0].audioLevel;
511
+        }
512
+    });
513
+
514
+    return audioLevels;
515
+};
516
+
490 517
 /**
491 518
  * Obtains local tracks for given {@link MediaType}. If the <tt>mediaType</tt>
492 519
  * argument is omitted the list of all local tracks will be returned.

+ 8
- 0
modules/browser/BrowserCapabilities.js Переглянути файл

@@ -122,6 +122,14 @@ export default class BrowserCapabilities extends BrowserDetection {
122 122
         return this.isChromiumBased() || this.isReactNative() || this.isSafari();
123 123
     }
124 124
 
125
+    /**
126
+     * Checks if the current browser supports audio level stats on the receivers.
127
+     */
128
+    supportsReceiverStats() {
129
+        return typeof window.RTCRtpReceiver !== 'undefined'
130
+            && Object.keys(RTCRtpReceiver.prototype).indexOf('getSynchronizationSources') > -1;
131
+    }
132
+
125 133
     /**
126 134
      * Checks if the current browser reports round trip time statistics for
127 135
      * the ICE candidate pair.

+ 22
- 5
modules/detection/NoAudioSignalDetection.js Переглянути файл

@@ -1,7 +1,8 @@
1 1
 import EventEmitter from 'events';
2 2
 
3 3
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
4
-
4
+import * as JitsiTrackEvents from '../../JitsiTrackEvents';
5
+import browser from '../browser';
5 6
 import * as DetectionEvents from './DetectionEvents';
6 7
 
7 8
 // We wait a certain time interval for constant silence input from the current device to account for
@@ -30,7 +31,9 @@ export default class NoAudioSignalDetection extends EventEmitter {
30 31
         this._timeoutTrigger = null;
31 32
         this._hasAudioInput = null;
32 33
 
33
-        conference.statistics.addAudioLevelListener(this._audioLevel.bind(this));
34
+        if (!browser.supportsReceiverStats()) {
35
+            conference.statistics.addAudioLevelListener(this._audioLevel.bind(this));
36
+        }
34 37
         conference.on(JitsiConferenceEvents.TRACK_ADDED, this._trackAdded.bind(this));
35 38
     }
36 39
 
@@ -58,7 +61,6 @@ export default class NoAudioSignalDetection extends EventEmitter {
58 61
         // the event.
59 62
         if (this._hasAudioInput === null || this._hasAudioInput !== status) {
60 63
             this._hasAudioInput = status;
61
-
62 64
             this.emit(DetectionEvents.AUDIO_INPUT_STATE_CHANGE, this._hasAudioInput);
63 65
         }
64 66
     }
@@ -103,7 +105,6 @@ export default class NoAudioSignalDetection extends EventEmitter {
103 105
         // Get currently active local tracks from the TraceablePeerConnection
104 106
         const localSSRCs = tpc.localSSRCs.get(this._audioTrack.rtcId);
105 107
 
106
-
107 108
         // Only target the current active track in the tpc. For some reason audio levels for previous
108 109
         // devices are also picked up from the PeerConnection so we filter them out.
109 110
         if (!localSSRCs || !localSSRCs.ssrcs.includes(ssrc)) {
@@ -114,7 +115,6 @@ export default class NoAudioSignalDetection extends EventEmitter {
114 115
         // can try to fire again.
115 116
         this._handleAudioInputStateChange(audioLevel);
116 117
         this._handleNoAudioInputDetection(audioLevel);
117
-
118 118
     }
119 119
 
120 120
     /**
@@ -129,6 +129,23 @@ export default class NoAudioSignalDetection extends EventEmitter {
129 129
             this._audioTrack = track;
130 130
             this._eventFired = false;
131 131
             this._clearTriggerTimeout();
132
+
133
+            // Listen for the audio levels on the newly added audio track
134
+            if (browser.supportsReceiverStats()) {
135
+                track.on(
136
+                    JitsiTrackEvents.NO_AUDIO_INPUT,
137
+                    audioLevel => {
138
+                        this._handleNoAudioInputDetection(audioLevel);
139
+                    }
140
+                );
141
+                track.on(
142
+                    JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,
143
+                    audioLevel => {
144
+                        this._handleNoAudioInputDetection(audioLevel);
145
+                        this._handleAudioInputStateChange(audioLevel);
146
+                    }
147
+                );
148
+            }
132 149
         }
133 150
     }
134 151
 }

+ 0
- 127
modules/detection/TalkMutedDetection.js Переглянути файл

@@ -1,127 +0,0 @@
1
-import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
2
-
3
-/**
4
- * The value which we use to say, every sound over this threshold
5
- * is talking on the mic.
6
- * @type {number}
7
- */
8
-const SPEECH_DETECT_THRESHOLD = 0.6;
9
-
10
-/**
11
- * Detect user trying to speek while is locally muted and fires an event.
12
- */
13
-export default class TalkMutedDetection {
14
-    /**
15
-     * Creates TalkMutedDetection
16
-     * @param conference the JitsiConference instance that created us.
17
-     * @param callback the callback to call when detected that the local user is
18
-     * talking while her microphone is muted.
19
-     * @constructor
20
-     */
21
-    constructor(conference, callback) {
22
-        /**
23
-         * The callback to call when detected that the local user is talking
24
-         * while her microphone is muted.
25
-         *
26
-         * @private
27
-         */
28
-        this._callback = callback;
29
-
30
-        /**
31
-         * The indicator which determines whether <tt>callback</tt> has been
32
-         * invoked for the current local audio track of <tt>conference</tt> so
33
-         * that it is invoked once only.
34
-         *
35
-         * @private
36
-         */
37
-        this._eventFired = false;
38
-
39
-        // XXX I went back and forth on the subject of where to put the access
40
-        // to statistics. On the one had, (1) statistics is likely intended to
41
-        // be private to conference and (2) there is a desire to keep the
42
-        // dependencies of modules to the minimum (i.e. not have
43
-        // TalkMutedDetection depend on statistics). On the other hand, (1)
44
-        // statistics is technically not private because
45
-        // JitsiConferenceEventManager accesses it and (2) TalkMutedDetection
46
-        // works exactly because it knows that there are no audio levels for
47
-        // JitsiLocalTrack but there are audio levels for the local participant
48
-        // through statistics.
49
-        conference.statistics.addAudioLevelListener(
50
-            this._audioLevel.bind(this));
51
-
52
-        conference.on(
53
-            JitsiConferenceEvents.TRACK_MUTE_CHANGED,
54
-            this._trackMuteChanged.bind(this));
55
-        conference.on(
56
-            JitsiConferenceEvents.TRACK_ADDED,
57
-            this._trackAdded.bind(this));
58
-    }
59
-
60
-    /* eslint-disable max-params */
61
-    /**
62
-     * Receives audio level events for all send and receive streams.
63
-     *
64
-     * @param {TraceablePeerConnection} pc - WebRTC PeerConnection object of the
65
-     * @param {number} ssrc - The synchronization source identifier (SSRC) of
66
-     * the endpoint/participant/stream being reported.
67
-     * @param {number} audioLevel - The audio level of <tt>ssrc</tt>.
68
-     * @param {boolean} isLocal - <tt>true</tt> if <tt>ssrc</tt> represents a
69
-     * local/send stream or <tt>false</tt> for a remote/receive stream.
70
-     */
71
-    _audioLevel(tpc, ssrc, audioLevel, isLocal) {
72
-        // We are interested in the local audio stream only and if event is not
73
-        // sent yet.
74
-        if (!isLocal || !this.audioTrack || this._eventFired) {
75
-            return;
76
-        }
77
-
78
-        if (this.audioTrack.isMuted()
79
-            && audioLevel > SPEECH_DETECT_THRESHOLD) {
80
-            this._eventFired = true;
81
-            this._callback();
82
-        }
83
-    }
84
-    /* eslint-enable max-params */
85
-
86
-    /**
87
-     * Determines whether a specific {@link JitsiTrack} represents a local audio
88
-     * track.
89
-     *
90
-     * @param {JitsiTrack} track - The <tt>JitsiTrack</tt> to be checked whether
91
-     * it represents a local audio track.
92
-     * @private
93
-     * @return {boolean} - <tt>true</tt> if the specified <tt>track</tt>
94
-     * represents a local audio track; otherwise, <tt>false</tt>.
95
-     */
96
-    _isLocalAudioTrack(track) {
97
-        return track.isAudioTrack() && track.isLocal();
98
-    }
99
-
100
-    /**
101
-     * Notifies this <tt>TalkMutedDetection</tt> that a {@link JitsiTrack} was
102
-     * added to the associated {@link JitsiConference}. Looks for the local
103
-     * audio track only.
104
-     *
105
-     * @param {JitsiTrack} track - The added <tt>JitsiTrack</tt>.
106
-     * @private
107
-     */
108
-    _trackAdded(track) {
109
-        if (this._isLocalAudioTrack(track)) {
110
-            this.audioTrack = track;
111
-        }
112
-    }
113
-
114
-    /**
115
-     * Notifies this <tt>TalkMutedDetection</tt> that the mute state of a
116
-     * {@link JitsiTrack} has changed. Looks for the local audio track only.
117
-     *
118
-     * @param {JitsiTrack} track - The <tt>JitsiTrack</tt> whose mute state has
119
-     * changed.
120
-     * @private
121
-     */
122
-    _trackMuteChanged(track) {
123
-        if (this._isLocalAudioTrack(track) && track.isMuted()) {
124
-            this._eventFired = false;
125
-        }
126
-    }
127
-}

+ 6
- 4
modules/statistics/LocalStatsCollector.js Переглянути файл

@@ -114,10 +114,12 @@ LocalStatsCollector.prototype.start = function() {
114 114
             analyser.getByteTimeDomainData(array);
115 115
             const audioLevel = timeDomainDataToAudioLevel(array);
116 116
 
117
-            if (audioLevel !== this.audioLevel) {
118
-                this.audioLevel = animateLevel(audioLevel, this.audioLevel);
119
-                this.callback(this.audioLevel);
120
-            }
117
+            // Set the audio levels always as NoAudioSignalDetection now
118
+            // uses audio levels from LocalStatsCollector and waits for
119
+            // atleast 4 secs for a no audio signal before displaying the
120
+            // notification on the UI.
121
+            this.audioLevel = animateLevel(audioLevel, this.audioLevel);
122
+            this.callback(this.audioLevel);
121 123
         },
122 124
         this.intervalMilis
123 125
     );

+ 40
- 22
modules/statistics/RTPStatsCollector.js Переглянути файл

@@ -291,31 +291,49 @@ StatsCollector.prototype.errorCallback = function(error) {
291 291
  */
292 292
 StatsCollector.prototype.start = function(startAudioLevelStats) {
293 293
     if (startAudioLevelStats) {
294
+        if (browser.supportsReceiverStats()) {
295
+            logger.info('Using RTCRtpSynchronizationSource for remote audio levels');
296
+        }
294 297
         this.audioLevelsIntervalId = setInterval(
295 298
             () => {
296
-                // Interval updates
297
-                this.peerconnection.getStats(
298
-                    report => {
299
-                        let results = null;
300
-
301
-                        if (!report || !report.result
302
-                            || typeof report.result !== 'function') {
303
-                            results = report;
304
-                        } else {
305
-                            results = report.result();
306
-                        }
307
-                        this.currentAudioLevelsReport = results;
308
-                        if (this._usesPromiseGetStats) {
309
-                            this.processNewAudioLevelReport();
310
-                        } else {
311
-                            this.processAudioLevelReport();
299
+                if (browser.supportsReceiverStats()) {
300
+                    const audioLevels = this.peerconnection.getAudioLevels();
301
+
302
+                    for (const ssrc in audioLevels) {
303
+                        if (audioLevels.hasOwnProperty(ssrc)) {
304
+                            this.eventEmitter.emit(
305
+                                StatisticsEvents.AUDIO_LEVEL,
306
+                                this.peerconnection,
307
+                                Number.parseInt(ssrc, 10),
308
+                                audioLevels[ssrc],
309
+                                false /* isLocal */);
312 310
                         }
313
-
314
-                        this.baselineAudioLevelsReport
315
-                            = this.currentAudioLevelsReport;
316
-                    },
317
-                    error => this.errorCallback(error)
318
-                );
311
+                    }
312
+                } else {
313
+                    // Interval updates
314
+                    this.peerconnection.getStats(
315
+                        report => {
316
+                            let results = null;
317
+
318
+                            if (!report || !report.result
319
+                                || typeof report.result !== 'function') {
320
+                                results = report;
321
+                            } else {
322
+                                results = report.result();
323
+                            }
324
+                            this.currentAudioLevelsReport = results;
325
+                            if (this._usesPromiseGetStats) {
326
+                                this.processNewAudioLevelReport();
327
+                            } else {
328
+                                this.processAudioLevelReport();
329
+                            }
330
+
331
+                            this.baselineAudioLevelsReport
332
+                                = this.currentAudioLevelsReport;
333
+                        },
334
+                        error => this.errorCallback(error)
335
+                    );
336
+                }
319 337
             },
320 338
             this.audioLevelsIntervalMilis
321 339
         );

Завантаження…
Відмінити
Зберегти