Selaa lähdekoodia

Fix codec negotiation and other multi-stream issues (#2133)

* fix(codec-selection) Munge all related m-lines for multi-stream.
Munge all the video m-lines to apply the preferred codec settings in multi-stream mode.

* fix(TPC) Cache transceiver mids for all local video tracks.
There will be multiple video tracks in multi-stream, we need to cache the mids for all the the associated transceivers.

* fix(sender-constraints) Do not apply sender constraints if value hasn't changed for a given source.

* fix(TPC) Configure the direction correctly for all m-lines.
Ignore negotiationneeded events for p2p to avoid going into a renegotiation loop.

* squash: Address review comments.
tags/v0.0.2
Jaya Allamsetty 3 vuotta sitten
vanhempi
commit
eb623e7536
No account linked to committer's email address

+ 72
- 57
modules/RTC/TraceablePeerConnection.js Näytä tiedosto

513
     return MediaDirection.INACTIVE;
513
     return MediaDirection.INACTIVE;
514
 };
514
 };
515
 
515
 
516
+/**
517
+ * Returns the MID of the m-line associated with the local desktop track (if it exists).
518
+ *
519
+ * @returns {Number|null}
520
+ */
521
+TraceablePeerConnection.prototype._getDesktopTrackMid = function() {
522
+    const desktopTrack = this.getLocalVideoTracks().find(track => track.getVideoType() === VideoType.DESKTOP);
523
+
524
+    if (desktopTrack) {
525
+        return Number(this._localTrackTransceiverMids.get(desktopTrack.rtcId));
526
+    }
527
+
528
+    return null;
529
+};
530
+
516
 /**
531
 /**
517
  * Returns the list of RTCRtpReceivers created for the source of the given media type associated with
532
  * Returns the list of RTCRtpReceivers created for the source of the given media type associated with
518
  * the set of remote endpoints specified.
533
  * the set of remote endpoints specified.
1639
     }
1654
     }
1640
 
1655
 
1641
     const parsedSdp = transform.parse(description.sdp);
1656
     const parsedSdp = transform.parse(description.sdp);
1657
+    const mLines = parsedSdp.media.filter(m => m.type === this.codecPreference.mediaType);
1642
 
1658
 
1643
-    // Only the m-line that defines the source the browser will be sending should need to change.
1644
-    // This is typically the first m-line with the matching media type.
1645
-    const mLine = parsedSdp.media.find(m => m.type === this.codecPreference.mediaType);
1646
-
1647
-    if (!mLine) {
1659
+    if (!mLines.length) {
1648
         return description;
1660
         return description;
1649
     }
1661
     }
1650
 
1662
 
1651
-    if (this.codecPreference.enable) {
1652
-        SDPUtil.preferCodec(mLine, this.codecPreference.mimeType);
1653
-
1654
-        // Strip the high profile H264 codecs on mobile clients for p2p connection.
1655
-        // High profile codecs give better quality at the expense of higher load which
1656
-        // we do not want on mobile clients.
1657
-        // Jicofo offers only the baseline code for the jvb connection.
1658
-        // TODO - add check for mobile browsers once js-utils provides that check.
1659
-        if (this.codecPreference.mimeType === CodecMimeType.H264 && browser.isReactNative() && this.isP2P) {
1660
-            SDPUtil.stripCodec(mLine, this.codecPreference.mimeType, true /* high profile */);
1663
+    for (const mLine of mLines) {
1664
+        if (this.codecPreference.enable) {
1665
+            SDPUtil.preferCodec(mLine, this.codecPreference.mimeType);
1666
+
1667
+            // Strip the high profile H264 codecs on mobile clients for p2p connection. High profile codecs give better
1668
+            // quality at the expense of higher load which we do not want on mobile clients. Jicofo offers only the
1669
+            // baseline code for the jvb connection and therefore this is not needed for jvb connection.
1670
+            // TODO - add check for mobile browsers once js-utils provides that check.
1671
+            if (this.codecPreference.mimeType === CodecMimeType.H264 && browser.isReactNative() && this.isP2P) {
1672
+                SDPUtil.stripCodec(mLine, this.codecPreference.mimeType, true /* high profile */);
1673
+            }
1674
+        } else {
1675
+            SDPUtil.stripCodec(mLine, this.codecPreference.mimeType);
1661
         }
1676
         }
1662
-    } else {
1663
-        SDPUtil.stripCodec(mLine, this.codecPreference.mimeType);
1664
     }
1677
     }
1665
 
1678
 
1666
     return new RTCSessionDescription({
1679
     return new RTCSessionDescription({
1989
         return;
2002
         return;
1990
     }
2003
     }
1991
 
2004
 
1992
-    for (const localTrack of localTracks) {
1993
-        const mediaType = localTrack.getType();
2005
+    [ MediaType.AUDIO, MediaType.VIDEO ].forEach(mediaType => {
2006
+        const tracks = localTracks.filter(t => t.getType() === mediaType);
1994
         const parsedSdp = transform.parse(localSdp);
2007
         const parsedSdp = transform.parse(localSdp);
1995
-        const mLine = parsedSdp.media.find(mline => mline.type === mediaType);
2008
+        const mLines = parsedSdp.media.filter(mline => mline.type === mediaType);
1996
 
2009
 
1997
-        if (!this._localTrackTransceiverMids.has(localTrack.rtcId)) {
1998
-            this._localTrackTransceiverMids.set(localTrack.rtcId, mLine.mid.toString());
1999
-        }
2000
-    }
2010
+        tracks.forEach((track, idx) => {
2011
+            if (!this._localTrackTransceiverMids.has(track.rtcId)) {
2012
+                this._localTrackTransceiverMids.set(track.rtcId, mLines[idx].mid.toString());
2013
+            }
2014
+        });
2015
+    });
2001
 };
2016
 };
2002
 
2017
 
2003
 /**
2018
 /**
2083
                     ? Promise.resolve()
2098
                     ? Promise.resolve()
2084
                     : this.tpcUtils.setEncodings(newTrack);
2099
                     : this.tpcUtils.setEncodings(newTrack);
2085
 
2100
 
2086
-                return configureEncodingsPromise.then(() => false);
2101
+                return configureEncodingsPromise.then(() => this.isP2P);
2087
             });
2102
             });
2088
     }
2103
     }
2089
 
2104
 
2240
     const transformer = new SdpTransformWrap(remoteDescription.sdp);
2255
     const transformer = new SdpTransformWrap(remoteDescription.sdp);
2241
 
2256
 
2242
     [ MediaType.AUDIO, MediaType.VIDEO ].forEach(mediaType => {
2257
     [ MediaType.AUDIO, MediaType.VIDEO ].forEach(mediaType => {
2243
-        const media = transformer.selectMedia(mediaType)?.[0];
2244
-        const hasLocalSource = this.hasAnyTracksOfType(mediaType);
2245
-        const hasRemoteSource = this.getRemoteTracks(null, mediaType).length > 0;
2246
-
2247
-        media.direction = hasLocalSource && hasRemoteSource
2248
-            ? MediaDirection.SENDRECV
2249
-            : hasLocalSource
2250
-                ? MediaDirection.RECVONLY
2251
-                : hasRemoteSource ? MediaDirection.SENDONLY : MediaDirection.INACTIVE;
2258
+        const media = transformer.selectMedia(mediaType);
2259
+        const localSources = this.getLocalTracks(mediaType).length;
2260
+        const remoteSources = this.getRemoteTracks(null, mediaType).length;
2261
+
2262
+        media.forEach((mLine, idx) => {
2263
+            if (localSources && localSources === remoteSources) {
2264
+                mLine.direction = MediaDirection.SENDRECV;
2265
+            } else if (!localSources && !remoteSources) {
2266
+                mLine.direction = MediaDirection.INACTIVE;
2267
+            } else if (!localSources) {
2268
+                mLine.direction = MediaDirection.SENDONLY;
2269
+            } else if (!remoteSources) {
2270
+                mLine.direction = MediaDirection.RECVONLY;
2271
+
2272
+            // When there are 2 local sources and 1 remote source, the first m-line should be set to 'sendrecv' while
2273
+            // the second one needs to be set to 'recvonly'.
2274
+            } else if (localSources > remoteSources) {
2275
+                mLine.direction = idx ? MediaDirection.RECVONLY : MediaDirection.SENDRECV;
2276
+
2277
+            // When there are 2 remote sources and 1 local source, the first m-line should be set to 'sendrecv' while
2278
+            // the second one needs to be set to 'sendonly'.
2279
+            } else {
2280
+                mLine.direction = idx ? MediaDirection.SENDONLY : MediaDirection.SENDRECV;
2281
+            }
2282
+        });
2252
     });
2283
     });
2253
 
2284
 
2254
     return new RTCSessionDescription({
2285
     return new RTCSessionDescription({
2398
         ? parsedSdp.media.filter(m => m.type === MediaType.VIDEO && m.direction !== direction)
2429
         ? parsedSdp.media.filter(m => m.type === MediaType.VIDEO && m.direction !== direction)
2399
         : [ parsedSdp.media.find(m => m.type === MediaType.VIDEO) ];
2430
         : [ parsedSdp.media.find(m => m.type === MediaType.VIDEO) ];
2400
 
2431
 
2401
-    // Find the mid associated with the desktop track so that bitrates can be configured accordingly on the
2402
-    // corresponding m-line.
2403
-    const getDesktopTrackMid = () => {
2404
-        const desktopTrack = this.getLocalVideoTracks().find(track => track.getVideoType() === VideoType.DESKTOP);
2405
-        let mid;
2406
-
2407
-        if (desktopTrack) {
2408
-            const trackIndex = Number(desktopTrack.getSourceName()?.split('-')[1].substring(1));
2409
-
2410
-            if (typeof trackIndex === 'number') {
2411
-                const transceiver = this.peerconnection.getTransceivers()
2412
-                    .filter(t => t.receiver.track.kind === MediaType.VIDEO
2413
-                        && t.direction !== MediaDirection.RECVONLY)[trackIndex];
2414
-
2415
-                mid = transceiver?.mid;
2416
-            }
2417
-        }
2418
-
2419
-        return Number(mid);
2420
-    };
2421
-
2422
     for (const mLine of mLines) {
2432
     for (const mLine of mLines) {
2423
         if (this.codecPreference.mimeType === CodecMimeType.VP9) {
2433
         if (this.codecPreference.mimeType === CodecMimeType.VP9) {
2424
             const bitrates = this.tpcUtils.videoBitrates.VP9 || this.tpcUtils.videoBitrates;
2434
             const bitrates = this.tpcUtils.videoBitrates.VP9 || this.tpcUtils.videoBitrates;
2425
             const hdBitrate = bitrates.high ? bitrates.high : HD_BITRATE;
2435
             const hdBitrate = bitrates.high ? bitrates.high : HD_BITRATE;
2426
             const mid = mLine.mid;
2436
             const mid = mLine.mid;
2427
             const isSharingScreen = FeatureFlags.isMultiStreamSupportEnabled()
2437
             const isSharingScreen = FeatureFlags.isMultiStreamSupportEnabled()
2428
-                ? mid === getDesktopTrackMid()
2438
+                ? mid === this._getDesktopTrackMid()
2429
                 : this._isSharingScreen();
2439
                 : this._isSharingScreen();
2430
             const limit = Math.floor((isSharingScreen ? HD_BITRATE : hdBitrate) / 1000);
2440
             const limit = Math.floor((isSharingScreen ? HD_BITRATE : hdBitrate) / 1000);
2431
 
2441
 
2635
     }
2645
     }
2636
 
2646
 
2637
     if (FeatureFlags.isSourceNameSignalingEnabled()) {
2647
     if (FeatureFlags.isSourceNameSignalingEnabled()) {
2638
-        this._senderMaxHeights.set(localVideoTrack.getSourceName(), frameHeight);
2648
+        const sourceName = localVideoTrack.getSourceName();
2649
+
2650
+        if (this._senderMaxHeights.get(sourceName) === frameHeight) {
2651
+            return Promise.resolve();
2652
+        }
2653
+        this._senderMaxHeights.set(sourceName, frameHeight);
2639
     } else {
2654
     } else {
2640
         this._senderVideoMaxHeight = frameHeight;
2655
         this._senderVideoMaxHeight = frameHeight;
2641
     }
2656
     }

+ 5
- 1
modules/qualitycontrol/SendVideoController.js Näytä tiedosto

104
                 if (track.getSourceName() === sourceName
104
                 if (track.getSourceName() === sourceName
105
                     && (!this._sourceSenderConstraints.has(sourceName)
105
                     && (!this._sourceSenderConstraints.has(sourceName)
106
                     || this._sourceSenderConstraints.get(sourceName) !== maxHeight)) {
106
                     || this._sourceSenderConstraints.get(sourceName) !== maxHeight)) {
107
-                    this._sourceSenderConstraints.set(sourceName, maxHeight);
107
+                    this._sourceSenderConstraints.set(
108
+                        sourceName,
109
+                        maxHeight === -1
110
+                            ? Math.min(MAX_LOCAL_RESOLUTION, this._preferredSendMaxFrameHeight)
111
+                            : maxHeight);
108
                     logger.debug(`Sender constraints for source:${sourceName} changed to maxHeight:${maxHeight}`);
112
                     logger.debug(`Sender constraints for source:${sourceName} changed to maxHeight:${maxHeight}`);
109
                     this._propagateSendMaxFrameHeight(sourceName);
113
                     this._propagateSendMaxFrameHeight(sourceName);
110
                 }
114
                 }

+ 5
- 9
modules/xmpp/JingleSessionPC.js Näytä tiedosto

636
             const remoteDescription = this.peerconnection.remoteDescription;
636
             const remoteDescription = this.peerconnection.remoteDescription;
637
 
637
 
638
             if (this.usesUnifiedPlan
638
             if (this.usesUnifiedPlan
639
+                && !this.isP2P
639
                 && state === 'stable'
640
                 && state === 'stable'
640
                 && remoteDescription
641
                 && remoteDescription
641
                 && typeof remoteDescription.sdp === 'string') {
642
                 && typeof remoteDescription.sdp === 'string') {
1188
             Promise.all(addTracks)
1189
             Promise.all(addTracks)
1189
                 .then(() => this._responderRenegotiate(remoteDescription))
1190
                 .then(() => this._responderRenegotiate(remoteDescription))
1190
                 .then(() => {
1191
                 .then(() => {
1191
-                    this.peerconnection.processLocalSdpForTransceiverInfo(localTracks);
1192
+                    this.peerconnection.processLocalSdpForTransceiverInfo(tracks);
1192
                     if (this.state === JingleSessionState.PENDING) {
1193
                     if (this.state === JingleSessionState.PENDING) {
1193
                         this.state = JingleSessionState.ACTIVE;
1194
                         this.state = JingleSessionState.ACTIVE;
1194
 
1195
 
2571
     }
2572
     }
2572
 
2573
 
2573
     /**
2574
     /**
2574
-     * Will put and execute on the queue a session modify task. Currently it
2575
-     * only checks the senders attribute of the video content in order to figure
2576
-     * out if the remote peer has video in the inactive state (stored locally
2577
-     * in {@link _remoteVideoActive} - see field description for more info).
2578
-     * @param {jQuery} jingleContents jQuery selector pointing to the jingle
2579
-     * element of the session modify IQ.
2580
-     * @see {@link _remoteVideoActive}
2581
-     * @see {@link _localVideoActive}
2575
+     * Will put and execute on the queue a session modify task. It checks if the sourceMaxFrameHeight (as requested by
2576
+     * the p2p peer) or the senders attribute of the video content has changed and modifies the local video sources
2577
+     * accordingly.
2582
      */
2578
      */
2583
     modifyContents(jingleContents) {
2579
     modifyContents(jingleContents) {
2584
         const newVideoSenders = JingleSessionPC.parseVideoSenders(jingleContents);
2580
         const newVideoSenders = JingleSessionPC.parseVideoSenders(jingleContents);

Loading…
Peruuta
Tallenna