浏览代码

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
dev1
Jaya Allamsetty 5 年前
父节点
当前提交
24097001aa

+ 69
- 31
modules/RTC/TraceablePeerConnection.js 查看文件

268
      */
268
      */
269
     this.senderVideoMaxHeight = null;
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
     // override as desired
295
     // override as desired
272
     this.trace = (what, info) => {
296
     this.trace = (what, info) => {
273
         logger.debug(what, info);
297
         logger.debug(what, info);
1469
     return this.localSSRCs.get(rtcId);
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
  * Checks if given track belongs to this peerconnection instance.
1535
  * Checks if given track belongs to this peerconnection instance.
1474
  *
1536
  *
1878
 
1940
 
1879
     this.trace('setLocalDescription::preTransform', dumpSDP(localSdp));
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
     if (browser.usesPlanB()) {
1947
     if (browser.usesPlanB()) {
1901
         localSdp = this._adjustLocalMediaDirection(localSdp);
1948
         localSdp = this._adjustLocalMediaDirection(localSdp);
2080
 TraceablePeerConnection.prototype.setRemoteDescription = function(description) {
2127
 TraceablePeerConnection.prototype.setRemoteDescription = function(description) {
2081
     this.trace('setRemoteDescription::preTransform', dumpSDP(description));
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
     if (browser.usesPlanB()) {
2134
     if (browser.usesPlanB()) {
2084
         // TODO the focus should squeze or explode the remote simulcast
2135
         // TODO the focus should squeze or explode the remote simulcast
2085
         if (this.isSimulcastOn()) {
2136
         if (this.isSimulcastOn()) {
2090
                 dumpSDP(description));
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
         // eslint-disable-next-line no-param-reassign
2144
         // eslint-disable-next-line no-param-reassign
2107
         description = normalizePlanB(description);
2145
         description = normalizePlanB(description);
2108
     } else {
2146
     } else {

+ 15
- 0
modules/browser/BrowserCapabilities.js 查看文件

104
         return !this.isFirefox() && !this.isSafari();
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
      * Checks if the current browser support the device change event.
123
      * Checks if the current browser support the device change event.
109
      * @return {boolean}
124
      * @return {boolean}

+ 2
- 0
modules/xmpp/JingleSessionPC.js 查看文件

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

+ 53
- 50
modules/xmpp/SDPUtil.js 查看文件

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
      * @param {string} codecName the name of the preferred codec
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
             return;
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
             const payloadTypes
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
      * types are also stripped. If the resulting mline would have no codecs,
603
      * types are also stripped. If the resulting mline would have no codecs,
613
      * it's disabled.
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
      * @param {string} codecName the name of the codec which will be stripped.
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
             return;
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
             if (rtp.codec
619
             if (rtp.codec
628
                 && rtp.codec.toLowerCase() === codecName.toLowerCase()) {
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
         if (removePts.length > 0) {
636
         if (removePts.length > 0) {
634
             // We also need to remove the payload types that are related to RTX
637
             // We also need to remove the payload types that are related to RTX
635
             // for the codecs we want to disable.
638
             // for the codecs we want to disable.
636
             const rtxApts = removePts.map(item => `apt=${item}`);
639
             const rtxApts = removePts.map(item => `apt=${item}`);
637
-            const rtxPts = videoMLine.fmtp.filter(
640
+            const rtxPts = mLine.fmtp.filter(
638
                 item => rtxApts.indexOf(item.config) !== -1);
641
                 item => rtxApts.indexOf(item.config) !== -1);
639
 
642
 
640
             removePts.push(...rtxPts.map(item => item.payload));
643
             removePts.push(...rtxPts.map(item => item.payload));
642
             // Call toString() on payloads to get around an issue within
645
             // Call toString() on payloads to get around an issue within
643
             // SDPTransform that sets payloads as a number, instead of a string,
646
             // SDPTransform that sets payloads as a number, instead of a string,
644
             // when there is only one payload.
647
             // when there is only one payload.
645
-            const allPts = videoMLine.payloads
648
+            const allPts = mLine.payloads
646
                 .toString()
649
                 .toString()
647
                 .split(' ')
650
                 .split(' ')
648
                 .map(Number);
651
                 .map(Number);
649
             const keepPts = allPts.filter(pt => removePts.indexOf(pt) === -1);
652
             const keepPts = allPts.filter(pt => removePts.indexOf(pt) === -1);
650
 
653
 
651
             if (keepPts.length === 0) {
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
             } else {
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
                 item => keepPts.indexOf(item.payload) !== -1);
664
                 item => keepPts.indexOf(item.payload) !== -1);
662
-            videoMLine.fmtp = videoMLine.fmtp.filter(
665
+            mLine.fmtp = mLine.fmtp.filter(
663
                 item => keepPts.indexOf(item.payload) !== -1);
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
                     item => keepPts.indexOf(item.payload) !== -1);
669
                     item => keepPts.indexOf(item.payload) !== -1);
667
             }
670
             }
668
         }
671
         }

+ 38
- 9
modules/xmpp/SDPUtil.spec.js 查看文件

9
         expect(parsed).toEqual('3jlcc1b3j1rqt6');
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
             const sdp = SampleSdpStrings.multiCodecVideoSdp;
14
             const sdp = SampleSdpStrings.multiCodecVideoSdp;
15
             const videoMLine = sdp.media.find(m => m.type === 'video');
15
             const videoMLine = sdp.media.find(m => m.type === 'video');
16
 
16
 
17
-            SDPUtil.preferVideoCodec(videoMLine, 'H264');
17
+            SDPUtil.preferCodec(videoMLine, 'H264');
18
             const newPayloadTypesOrder
18
             const newPayloadTypesOrder
19
                 = videoMLine.payloads.split(' ').map(
19
                 = videoMLine.payloads.split(' ').map(
20
                     ptStr => parseInt(ptStr, 10));
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
             const sdp = SampleSdpStrings.multiCodecVideoSdp;
44
             const sdp = SampleSdpStrings.multiCodecVideoSdp;
29
             const videoMLine = sdp.media.find(m => m.type === 'video');
45
             const videoMLine = sdp.media.find(m => m.type === 'video');
30
 
46
 
31
-            SDPUtil.stripVideoCodec(videoMLine, 'H264');
47
+            SDPUtil.stripCodec(videoMLine, 'H264');
32
             const newPayloadTypes = videoMLine.payloads.split(' ').map(Number);
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 查看文件

70
 
70
 
71
 // A basic sdp video mline with a single stream and multiple codecs
71
 // A basic sdp video mline with a single stream and multiple codecs
72
 const multiCodecVideoMLine = ''
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
 + 'c=IN IP4 0.0.0.0\r\n'
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
 + 'a=rtcp:9 IN IP4 0.0.0.0\r\n'
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
 + 'a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n'
110
 + 'a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n'
86
 + 'a=setup:passive\r\n'
111
 + 'a=setup:passive\r\n'
87
 + 'a=mid:video\r\n'
112
 + 'a=mid:video\r\n'

正在加载...
取消
保存