Ver código fonte

ref(sdp): Move all instances of a preferred codec up the list in the SDP

Make sdp munging logic for changing codec preferences more generic
Do not negotiate High profile H264 codecs on when RN clients are in the call
master
Jaya Allamsetty 4 anos atrás
pai
commit
24097001aa

+ 69
- 31
modules/RTC/TraceablePeerConnection.js Ver arquivo

@@ -268,6 +268,30 @@ export default function TraceablePeerConnection(
268 268
      */
269 269
     this.senderVideoMaxHeight = null;
270 270
 
271
+    // Set the codec preference that will be applied on the SDP
272
+    // based on the config.js settings.
273
+    if (this.options.preferH264 || this.options.preferVP9) {
274
+        this.codecPreference = {
275
+            enable: true,
276
+            mediaType: MediaType.VIDEO,
277
+            mimeType: this.options.preferH264
278
+                ? 'h264'
279
+                : 'vp9'
280
+        };
281
+    }
282
+
283
+    // If both enable and disable are set for the same codec, disable
284
+    // setting will prevail.
285
+    if (this.options.disableH264 || this.options.disableVP9) {
286
+        this.codecPreference = {
287
+            enable: false,
288
+            mediaType: MediaType.VIDEO,
289
+            mimeType: this.options.disableH264
290
+                ? 'h264'
291
+                : 'vp9'
292
+        };
293
+    }
294
+
271 295
     // override as desired
272 296
     this.trace = (what, info) => {
273 297
         logger.debug(what, info);
@@ -1469,6 +1493,44 @@ TraceablePeerConnection.prototype._getSSRC = function(rtcId) {
1469 1493
     return this.localSSRCs.get(rtcId);
1470 1494
 };
1471 1495
 
1496
+/**
1497
+ * Munges the order of the codecs in the SDP passed based on the preference
1498
+ * set through config.js settings. All instances of the specified codec are
1499
+ * moved up to the top of the list when it is preferred. The specified codec
1500
+ * is deleted from the list if the configuration specifies that the codec be
1501
+ * disabled.
1502
+ * @param {RTCSessionDescription} description that needs to be munged.
1503
+ * @returns {RTCSessionDescription} the munged description.
1504
+ */
1505
+TraceablePeerConnection.prototype._mungeCodecOrder = function(description) {
1506
+    if (!this.codecPreference || browser.supportsCodecPreferences()) {
1507
+        return description;
1508
+    }
1509
+
1510
+    const parsedSdp = transform.parse(description.sdp);
1511
+    const mLine = parsedSdp.media.find(m => m.type === this.codecPreference.mediaType);
1512
+
1513
+    if (this.codecPreference.enable && this.codecPreference.mimeType) {
1514
+        SDPUtil.preferCodec(mLine, this.codecPreference.mimeType);
1515
+
1516
+        // Strip the high profile H264 codecs on mobile clients for p2p connection.
1517
+        // High profile codecs give better quality at the expense of higher load which
1518
+        // we do not want on mobile clients.
1519
+        // Jicofo offers only the baseline code for the jvb connection.
1520
+        // TODO - add check for mobile browsers once js-utils provides that check.
1521
+        if (this.codecPreference.mimeType === 'h264' && browser.isReactNative() && this.isP2P) {
1522
+            SDPUtil.stripCodec(mLine, this.codecPreference.mimeType, true /* high profile */);
1523
+        }
1524
+    } else if (this.codecPreference.mimeType) {
1525
+        SDPUtil.stripCodec(mLine, this.codecPreference.mimeType);
1526
+    }
1527
+
1528
+    return new RTCSessionDescription({
1529
+        type: description.type,
1530
+        sdp: transform.write(parsedSdp)
1531
+    });
1532
+};
1533
+
1472 1534
 /**
1473 1535
  * Checks if given track belongs to this peerconnection instance.
1474 1536
  *
@@ -1878,24 +1940,9 @@ TraceablePeerConnection.prototype.setLocalDescription = function(description) {
1878 1940
 
1879 1941
     this.trace('setLocalDescription::preTransform', dumpSDP(localSdp));
1880 1942
 
1881
-    if (this.options.disableH264 || this.options.preferH264) {
1882
-        const parsedSdp = transform.parse(localSdp.sdp);
1883
-        const videoMLine = parsedSdp.media.find(m => m.type === 'video');
1884
-
1885
-        if (this.options.disableH264) {
1886
-            SDPUtil.stripVideoCodec(videoMLine, 'h264');
1887
-        } else {
1888
-            SDPUtil.preferVideoCodec(videoMLine, 'h264');
1889
-        }
1890
-
1891
-        localSdp = new RTCSessionDescription({
1892
-            type: localSdp.type,
1893
-            sdp: transform.write(parsedSdp)
1894
-        });
1895
-
1896
-        this.trace('setLocalDescription::postTransform (H264)',
1897
-            dumpSDP(localSdp));
1898
-    }
1943
+    // Munge the order of the codecs based on the preferences set through config.js
1944
+    // eslint-disable-next-line no-param-reassign
1945
+    description = this._mungeCodecOrder(localSdp);
1899 1946
 
1900 1947
     if (browser.usesPlanB()) {
1901 1948
         localSdp = this._adjustLocalMediaDirection(localSdp);
@@ -2080,6 +2127,10 @@ TraceablePeerConnection.prototype.setMaxBitRate = function(localTrack = null) {
2080 2127
 TraceablePeerConnection.prototype.setRemoteDescription = function(description) {
2081 2128
     this.trace('setRemoteDescription::preTransform', dumpSDP(description));
2082 2129
 
2130
+    // Munge the order of the codecs based on the preferences set through config.js
2131
+    // eslint-disable-next-line no-param-reassign
2132
+    description = this._mungeCodecOrder(description);
2133
+
2083 2134
     if (browser.usesPlanB()) {
2084 2135
         // TODO the focus should squeze or explode the remote simulcast
2085 2136
         if (this.isSimulcastOn()) {
@@ -2090,19 +2141,6 @@ TraceablePeerConnection.prototype.setRemoteDescription = function(description) {
2090 2141
                 dumpSDP(description));
2091 2142
         }
2092 2143
 
2093
-        if (this.options.preferH264) {
2094
-            const parsedSdp = transform.parse(description.sdp);
2095
-            const videoMLine = parsedSdp.media.find(m => m.type === 'video');
2096
-
2097
-            SDPUtil.preferVideoCodec(videoMLine, 'h264');
2098
-
2099
-            // eslint-disable-next-line no-param-reassign
2100
-            description = new RTCSessionDescription({
2101
-                type: description.type,
2102
-                sdp: transform.write(parsedSdp)
2103
-            });
2104
-        }
2105
-
2106 2144
         // eslint-disable-next-line no-param-reassign
2107 2145
         description = normalizePlanB(description);
2108 2146
     } else {

+ 15
- 0
modules/browser/BrowserCapabilities.js Ver arquivo

@@ -104,6 +104,21 @@ export default class BrowserCapabilities extends BrowserDetection {
104 104
         return !this.isFirefox() && !this.isSafari();
105 105
     }
106 106
 
107
+    /**
108
+     * Checks if the current browser supports setting codec preferences on the transceiver.
109
+     * @returns {boolean}
110
+     */
111
+    supportsCodecPreferences() {
112
+        return this.usesUnifiedPlan()
113
+            && typeof window.RTCRtpTransceiver !== 'undefined'
114
+            && Object.keys(window.RTCRtpTransceiver.prototype).indexOf('setCodecPreferences') > -1
115
+            && Object.keys(RTCRtpSender.prototype).indexOf('getCapabilities') > -1
116
+
117
+            // this is not working on Safari because of the following bug
118
+            // https://bugs.webkit.org/show_bug.cgi?id=215567
119
+            && !this.isSafari();
120
+    }
121
+
107 122
     /**
108 123
      * Checks if the current browser support the device change event.
109 124
      * @return {boolean}

+ 2
- 0
modules/xmpp/JingleSessionPC.js Ver arquivo

@@ -336,6 +336,7 @@ export default class JingleSessionPC extends JingleSession {
336 336
             pcOptions.disableSimulcast = true;
337 337
             pcOptions.disableH264 = options.p2p && options.p2p.disableH264;
338 338
             pcOptions.preferH264 = options.p2p && options.p2p.preferH264;
339
+            pcOptions.preferVP9 = options.p2p && options.p2p.preferVP9;
339 340
 
340 341
             const abtestSuspendVideo = this._abtestSuspendVideoEnabled(options);
341 342
 
@@ -348,6 +349,7 @@ export default class JingleSessionPC extends JingleSession {
348 349
                 = options.disableSimulcast
349 350
                     || (options.preferH264 && !options.disableH264);
350 351
             pcOptions.preferH264 = options.preferH264;
352
+            pcOptions.preferVP9 = options.preferVP9;
351 353
 
352 354
             // disable simulcast for screenshare and set the max bitrate to
353 355
             // 500Kbps if the testing flag is present in config.js.

+ 53
- 50
modules/xmpp/SDPUtil.js Ver arquivo

@@ -564,46 +564,37 @@ const SDPUtil = {
564 564
     },
565 565
 
566 566
     /**
567
-     * Sets the given codecName as the preferred codec by
568
-     *  moving it to the beginning of the payload types
569
-     *  list (modifies the given mline in place).  If there
570
-     *  are multiple options within the same codec (multiple h264
571
-     *  profiles, for instance), this will prefer the first one
572
-     *  that is found.
573
-     * @param {object} videoMLine the video mline object from
574
-     *  an sdp as parsed by transform.parse
567
+     * Sets the given codecName as the preferred codec by moving it to the beginning
568
+     * of the payload types list (modifies the given mline in place). All instances
569
+     * of the codec are moved up.
570
+     * @param {object} mLine the mline object from an sdp as parsed by transform.parse
575 571
      * @param {string} codecName the name of the preferred codec
576 572
      */
577
-    preferVideoCodec(videoMLine, codecName) {
578
-        let payloadType = null;
579
-
580
-        if (!videoMLine || !codecName) {
573
+    preferCodec(mline, codecName) {
574
+        if (!mline || !codecName) {
581 575
             return;
582 576
         }
583 577
 
584
-        for (let i = 0; i < videoMLine.rtp.length; ++i) {
585
-            const rtp = videoMLine.rtp[i];
578
+        const matchingPayloadTypes = mline.rtp
579
+            .filter(rtp => rtp.codec && rtp.codec.toLowerCase() === codecName.toLowerCase())
580
+            .map(rtp => rtp.payload);
586 581
 
587
-            if (rtp.codec
588
-                && rtp.codec.toLowerCase() === codecName.toLowerCase()) {
589
-                payloadType = rtp.payload;
590
-                break;
591
-            }
592
-        }
593
-        if (payloadType) {
594
-            // Call toString() on payloads to get around an issue within
595
-            // SDPTransform that sets payloads as a number, instead of a string,
596
-            // when there is only one payload.
582
+        if (matchingPayloadTypes) {
583
+            // Call toString() on payloads to get around an issue within SDPTransform that sets
584
+            // payloads as a number, instead of a string, when there is only one payload.
597 585
             const payloadTypes
598
-                = videoMLine.payloads
599
-                    .toString()
600
-                    .split(' ')
601
-                    .map(p => parseInt(p, 10));
602
-            const payloadIndex = payloadTypes.indexOf(payloadType);
586
+                = mline.payloads
587
+                .toString()
588
+                .split(' ')
589
+                .map(p => parseInt(p, 10));
590
+
591
+            for (const pt of matchingPayloadTypes.reverse()) {
592
+                const payloadIndex = payloadTypes.indexOf(pt);
603 593
 
604
-            payloadTypes.splice(payloadIndex, 1);
605
-            payloadTypes.unshift(payloadType);
606
-            videoMLine.payloads = payloadTypes.join(' ');
594
+                payloadTypes.splice(payloadIndex, 1);
595
+                payloadTypes.unshift(pt);
596
+            }
597
+            mline.payloads = payloadTypes.join(' ');
607 598
         }
608 599
     },
609 600
 
@@ -612,29 +603,41 @@ const SDPUtil = {
612 603
      * types are also stripped. If the resulting mline would have no codecs,
613 604
      * it's disabled.
614 605
      *
615
-     * @param {object} videoMLine the video mline object from an sdp as parsed
616
-     * by transform.parse.
606
+     * @param {object} mLine the mline object from an sdp as parsed by transform.parse.
617 607
      * @param {string} codecName the name of the codec which will be stripped.
618 608
      */
619
-    stripVideoCodec(videoMLine, codecName) {
620
-        if (!videoMLine || !codecName) {
609
+    stripCodec(mLine, codecName, highProfile = false) {
610
+        if (!mLine || !codecName) {
621 611
             return;
622 612
         }
623 613
 
624
-        const removePts = [];
614
+        const h264Pts = [];
615
+        let removePts = [];
616
+        const stripH264HighCodec = codecName.toLowerCase() === 'h264' && highProfile;
625 617
 
626
-        for (const rtp of videoMLine.rtp) {
618
+        for (const rtp of mLine.rtp) {
627 619
             if (rtp.codec
628 620
                 && rtp.codec.toLowerCase() === codecName.toLowerCase()) {
629
-                removePts.push(rtp.payload);
621
+                if (stripH264HighCodec) {
622
+                    h264Pts.push(rtp.payload);
623
+                } else {
624
+                    removePts.push(rtp.payload);
625
+                }
630 626
             }
631 627
         }
632 628
 
629
+        // high profile H264 codecs have 64 as the first two bytes of the profile-level-id.
630
+        if (stripH264HighCodec) {
631
+            removePts = mLine.fmtp
632
+                .filter(item => h264Pts.indexOf(item.payload) > -1 && item.config.includes('profile-level-id=64'))
633
+                .map(item => item.payload);
634
+        }
635
+
633 636
         if (removePts.length > 0) {
634 637
             // We also need to remove the payload types that are related to RTX
635 638
             // for the codecs we want to disable.
636 639
             const rtxApts = removePts.map(item => `apt=${item}`);
637
-            const rtxPts = videoMLine.fmtp.filter(
640
+            const rtxPts = mLine.fmtp.filter(
638 641
                 item => rtxApts.indexOf(item.config) !== -1);
639 642
 
640 643
             removePts.push(...rtxPts.map(item => item.payload));
@@ -642,27 +645,27 @@ const SDPUtil = {
642 645
             // Call toString() on payloads to get around an issue within
643 646
             // SDPTransform that sets payloads as a number, instead of a string,
644 647
             // when there is only one payload.
645
-            const allPts = videoMLine.payloads
648
+            const allPts = mLine.payloads
646 649
                 .toString()
647 650
                 .split(' ')
648 651
                 .map(Number);
649 652
             const keepPts = allPts.filter(pt => removePts.indexOf(pt) === -1);
650 653
 
651 654
             if (keepPts.length === 0) {
652
-                // There are no other video codecs, disable the stream.
653
-                videoMLine.port = 0;
654
-                videoMLine.direction = 'inactive';
655
-                videoMLine.payloads = '*';
655
+                // There are no other codecs, disable the stream.
656
+                mLine.port = 0;
657
+                mLine.direction = 'inactive';
658
+                mLine.payloads = '*';
656 659
             } else {
657
-                videoMLine.payloads = keepPts.join(' ');
660
+                mLine.payloads = keepPts.join(' ');
658 661
             }
659 662
 
660
-            videoMLine.rtp = videoMLine.rtp.filter(
663
+            mLine.rtp = mLine.rtp.filter(
661 664
                 item => keepPts.indexOf(item.payload) !== -1);
662
-            videoMLine.fmtp = videoMLine.fmtp.filter(
665
+            mLine.fmtp = mLine.fmtp.filter(
663 666
                 item => keepPts.indexOf(item.payload) !== -1);
664
-            if (videoMLine.rtcpFb) {
665
-                videoMLine.rtcpFb = videoMLine.rtcpFb.filter(
667
+            if (mLine.rtcpFb) {
668
+                mLine.rtcpFb = mLine.rtcpFb.filter(
666 669
                     item => keepPts.indexOf(item.payload) !== -1);
667 670
             }
668 671
         }

+ 38
- 9
modules/xmpp/SDPUtil.spec.js Ver arquivo

@@ -9,30 +9,59 @@ describe('SDPUtil', () => {
9 9
         expect(parsed).toEqual('3jlcc1b3j1rqt6');
10 10
     });
11 11
 
12
-    describe('preferVideoCodec', () => {
13
-        it('should move a preferred codec to the front', () => {
12
+    describe('preferCodec for video codec', () => {
13
+        it('should move a preferred video codec to the front', () => {
14 14
             const sdp = SampleSdpStrings.multiCodecVideoSdp;
15 15
             const videoMLine = sdp.media.find(m => m.type === 'video');
16 16
 
17
-            SDPUtil.preferVideoCodec(videoMLine, 'H264');
17
+            SDPUtil.preferCodec(videoMLine, 'H264');
18 18
             const newPayloadTypesOrder
19 19
                 = videoMLine.payloads.split(' ').map(
20 20
                     ptStr => parseInt(ptStr, 10));
21 21
 
22
-            expect(newPayloadTypesOrder[0]).toEqual(126);
22
+            expect(newPayloadTypesOrder[0]).toEqual(102);
23
+            expect(newPayloadTypesOrder[1]).toEqual(127);
23 24
         });
24 25
     });
25 26
 
26
-    describe('stripVideoCodec', () => {
27
-        it('should remove a codec', () => {
27
+    describe('preferCodec for audio codec', () => {
28
+        it('should move a preferred audio codec to the front', () => {
29
+            const sdp = SampleSdpStrings.multiCodecVideoSdp;
30
+            const audioMLine = sdp.media.find(m => m.type === 'audio');
31
+
32
+            SDPUtil.preferCodec(audioMLine, 'ISAC');
33
+            const newPayloadTypesOrder
34
+                = audioMLine.payloads.split(' ').map(
35
+                    ptStr => parseInt(ptStr, 10));
36
+
37
+            expect(newPayloadTypesOrder[0]).toEqual(103);
38
+            expect(newPayloadTypesOrder[1]).toEqual(104);
39
+        });
40
+    });
41
+
42
+    describe('strip Video Codec', () => {
43
+        it('should remove a video codec', () => {
28 44
             const sdp = SampleSdpStrings.multiCodecVideoSdp;
29 45
             const videoMLine = sdp.media.find(m => m.type === 'video');
30 46
 
31
-            SDPUtil.stripVideoCodec(videoMLine, 'H264');
47
+            SDPUtil.stripCodec(videoMLine, 'H264');
32 48
             const newPayloadTypes = videoMLine.payloads.split(' ').map(Number);
33 49
 
34
-            expect(newPayloadTypes.length).toEqual(1);
35
-            expect(newPayloadTypes[0]).toEqual(100);
50
+            expect(newPayloadTypes.length).toEqual(4);
51
+            expect(newPayloadTypes[0]).toEqual(96);
52
+        });
53
+    });
54
+
55
+    describe('strip Audio Codec', () => {
56
+        it('should remove an audio codec', () => {
57
+            const sdp = SampleSdpStrings.multiCodecVideoSdp;
58
+            const audioMLine = sdp.media.find(m => m.type === 'audio');
59
+
60
+            SDPUtil.stripCodec(audioMLine, 'OPUS');
61
+            const newPayloadTypes = audioMLine.payloads.split(' ').map(Number);
62
+
63
+            expect(newPayloadTypes.length).toEqual(3);
64
+            expect(newPayloadTypes[0]).toEqual(103);
36 65
         });
37 66
     });
38 67
 });

+ 35
- 10
modules/xmpp/SampleSdpStrings.js Ver arquivo

@@ -70,18 +70,43 @@ const plainVideoMLineSdp = ''
70 70
 
71 71
 // A basic sdp video mline with a single stream and multiple codecs
72 72
 const multiCodecVideoMLine = ''
73
-+ 'm=video 9 RTP/SAVPF 100 126 97\r\n'
73
++ 'm=video 9 RTP/SAVPF 96 97 98 99 102 121 127 120\r\n'
74 74
 + 'c=IN IP4 0.0.0.0\r\n'
75
-+ 'a=rtpmap:100 VP8/90000\r\n'
76
-+ 'a=rtpmap:126 H264/90000\r\n'
77
-+ 'a=rtpmap:97 H264/90000\r\n'
75
++ 'a=rtpmap:96 VP8/90000\r\n'
76
++ 'a=rtpmap:97 rtx/90000\r\n'
77
++ 'a=rtpmap:98 VP9/90000\r\n'
78
++ 'a=rtpmap:99 rtx/90000\r\n'
79
++ 'a=rtpmap:102 H264/90000\r\n'
80
++ 'a=rtpmap:121 rtx/90000\r\n'
81
++ 'a=rtpmap:127 H264/90000\r\n'
82
++ 'a=rtpmap:120 rtx/90000\r\n'
78 83
 + 'a=rtcp:9 IN IP4 0.0.0.0\r\n'
79
-+ 'a=rtcp-fb:100 ccm fir\r\n'
80
-+ 'a=rtcp-fb:100 nack\r\n'
81
-+ 'a=rtcp-fb:100 nack pli\r\n'
82
-+ 'a=rtcp-fb:100 goog-remb\r\n'
83
-+ 'a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1\r\n'
84
-+ 'a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1\r\n'
84
++ 'a=rtcp-fb:96 ccm fir\r\n'
85
++ 'a=rtcp-fb:96 transport-cc\r\n'
86
++ 'a=rtcp-fb:96 nack\r\n'
87
++ 'a=rtcp-fb:96 nack pli\r\n'
88
++ 'a=rtcp-fb:96 goog-remb\r\n'
89
++ 'a=rtcp-fb:98 ccm fir\r\n'
90
++ 'a=rtcp-fb:98 transport-cc\r\n'
91
++ 'a=rtcp-fb:98 nack\r\n'
92
++ 'a=rtcp-fb:98 nack pli\r\n'
93
++ 'a=rtcp-fb:98 goog-remb\r\n'
94
++ 'a=rtcp-fb:102 ccm fir\r\n'
95
++ 'a=rtcp-fb:102 transport-cc\r\n'
96
++ 'a=rtcp-fb:102 nack\r\n'
97
++ 'a=rtcp-fb:102 nack pli\r\n'
98
++ 'a=rtcp-fb:102 goog-remb\r\n'
99
++ 'a=rtcp-fb:127 ccm fir\r\n'
100
++ 'a=rtcp-fb:127 transport-cc\r\n'
101
++ 'a=rtcp-fb:127 nack\r\n'
102
++ 'a=rtcp-fb:127 nack pli\r\n'
103
++ 'a=rtcp-fb:127 goog-remb\r\n'
104
++ 'a=fmtp:97 apt=96\r\n'
105
++ 'a=fmtp:98 profile-id=0\r\n'
106
++ 'a=fmtp:102 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1\r\n'
107
++ 'a=fmtp:121 apt=102\r\n'
108
++ 'a=fmtp:127 profile-level-id=42e01f;level-asymmetry-allowed=1:packetization-mode=0\r\n'
109
++ 'a=fmtp:120 apt=127\r\n'
85 110
 + 'a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n'
86 111
 + 'a=setup:passive\r\n'
87 112
 + 'a=mid:video\r\n'

Carregando…
Cancelar
Salvar