Browse Source

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 years ago
parent
commit
eb623e7536
No account linked to committer's email address

+ 72
- 57
modules/RTC/TraceablePeerConnection.js View File

@@ -513,6 +513,21 @@ TraceablePeerConnection.prototype.getDesiredMediaDirection = function(mediaType,
513 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 532
  * Returns the list of RTCRtpReceivers created for the source of the given media type associated with
518 533
  * the set of remote endpoints specified.
@@ -1639,28 +1654,26 @@ TraceablePeerConnection.prototype._mungeCodecOrder = function(description) {
1639 1654
     }
1640 1655
 
1641 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 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 1679
     return new RTCSessionDescription({
@@ -1989,15 +2002,17 @@ TraceablePeerConnection.prototype.processLocalSdpForTransceiverInfo = function(l
1989 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 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,7 +2098,7 @@ TraceablePeerConnection.prototype.replaceTrack = function(oldTrack, newTrack) {
2083 2098
                     ? Promise.resolve()
2084 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,15 +2255,31 @@ TraceablePeerConnection.prototype._adjustRemoteMediaDirection = function(remoteD
2240 2255
     const transformer = new SdpTransformWrap(remoteDescription.sdp);
2241 2256
 
2242 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 2285
     return new RTCSessionDescription({
@@ -2398,34 +2429,13 @@ TraceablePeerConnection.prototype._setVp9MaxBitrates = function(description, isL
2398 2429
         ? parsedSdp.media.filter(m => m.type === MediaType.VIDEO && m.direction !== direction)
2399 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 2432
     for (const mLine of mLines) {
2423 2433
         if (this.codecPreference.mimeType === CodecMimeType.VP9) {
2424 2434
             const bitrates = this.tpcUtils.videoBitrates.VP9 || this.tpcUtils.videoBitrates;
2425 2435
             const hdBitrate = bitrates.high ? bitrates.high : HD_BITRATE;
2426 2436
             const mid = mLine.mid;
2427 2437
             const isSharingScreen = FeatureFlags.isMultiStreamSupportEnabled()
2428
-                ? mid === getDesktopTrackMid()
2438
+                ? mid === this._getDesktopTrackMid()
2429 2439
                 : this._isSharingScreen();
2430 2440
             const limit = Math.floor((isSharingScreen ? HD_BITRATE : hdBitrate) / 1000);
2431 2441
 
@@ -2635,7 +2645,12 @@ TraceablePeerConnection.prototype.setSenderVideoConstraints = function(frameHeig
2635 2645
     }
2636 2646
 
2637 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 2654
     } else {
2640 2655
         this._senderVideoMaxHeight = frameHeight;
2641 2656
     }

+ 5
- 1
modules/qualitycontrol/SendVideoController.js View File

@@ -104,7 +104,11 @@ export default class SendVideoController {
104 104
                 if (track.getSourceName() === sourceName
105 105
                     && (!this._sourceSenderConstraints.has(sourceName)
106 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 112
                     logger.debug(`Sender constraints for source:${sourceName} changed to maxHeight:${maxHeight}`);
109 113
                     this._propagateSendMaxFrameHeight(sourceName);
110 114
                 }

+ 5
- 9
modules/xmpp/JingleSessionPC.js View File

@@ -636,6 +636,7 @@ export default class JingleSessionPC extends JingleSession {
636 636
             const remoteDescription = this.peerconnection.remoteDescription;
637 637
 
638 638
             if (this.usesUnifiedPlan
639
+                && !this.isP2P
639 640
                 && state === 'stable'
640 641
                 && remoteDescription
641 642
                 && typeof remoteDescription.sdp === 'string') {
@@ -1188,7 +1189,7 @@ export default class JingleSessionPC extends JingleSession {
1188 1189
             Promise.all(addTracks)
1189 1190
                 .then(() => this._responderRenegotiate(remoteDescription))
1190 1191
                 .then(() => {
1191
-                    this.peerconnection.processLocalSdpForTransceiverInfo(localTracks);
1192
+                    this.peerconnection.processLocalSdpForTransceiverInfo(tracks);
1192 1193
                     if (this.state === JingleSessionState.PENDING) {
1193 1194
                         this.state = JingleSessionState.ACTIVE;
1194 1195
 
@@ -2571,14 +2572,9 @@ export default class JingleSessionPC extends JingleSession {
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 2579
     modifyContents(jingleContents) {
2584 2580
         const newVideoSenders = JingleSessionPC.parseVideoSenders(jingleContents);

Loading…
Cancel
Save