tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RTCRtpParameters-codecs.html (21103B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <meta name="timeout" content="long">
      4 <title>RTCRtpParameters codecs</title>
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="dictionary-helper.js"></script>
      8 <script src="RTCRtpParameters-helper.js"></script>
      9 <script src="RTCPeerConnection-helper.js"></script>
     10 <script src="./third_party/sdp/sdp.js"></script>
     11 <script>
     12  'use strict';
     13 
     14  // Test is based on the following editor draft:
     15  // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
     16 
     17  // The following helper functions are called from RTCRtpParameters-helper.js:
     18  //   doOfferAnswerExchange
     19  //   validateSenderRtpParameters
     20 
     21  /*
     22    5.2.  RTCRtpSender Interface
     23      interface RTCRtpSender {
     24        Promise<void>           setParameters(optional RTCRtpParameters parameters);
     25        RTCRtpParameters        getParameters();
     26      };
     27 
     28      dictionary RTCRtpParameters {
     29        DOMString                                 transactionId;
     30        sequence<RTCRtpEncodingParameters>        encodings;
     31        sequence<RTCRtpHeaderExtensionParameters> headerExtensions;
     32        RTCRtcpParameters                         rtcp;
     33        sequence<RTCRtpCodecParameters>           codecs;
     34      };
     35 
     36      dictionary RTCRtpCodecParameters {
     37        [readonly]
     38        unsigned short payloadType;
     39 
     40        [readonly]
     41        DOMString      mimeType;
     42 
     43        [readonly]
     44        unsigned long  clockRate;
     45 
     46        [readonly]
     47        unsigned short channels;
     48 
     49        [readonly]
     50        DOMString      sdpFmtpLine;
     51      };
     52 
     53      getParameters
     54          - The codecs sequence is populated based on the codecs that have been negotiated
     55            for sending, and which the user agent is currently capable of sending.
     56 
     57            If setParameters has removed or reordered codecs, getParameters MUST return
     58            the shortened/reordered list. However, every time codecs are renegotiated by
     59            a new offer/answer exchange, the list of codecs MUST be restored to the full
     60            negotiated set, in the priority order indicated by the remote description,
     61            in effect discarding the effects of setParameters.
     62 
     63      codecs
     64        - When using the setParameters method, the codecs sequence from the corresponding
     65          call to getParameters can be reordered and entries can be removed, but entries
     66          cannot be added, and the RTCRtpCodecParameters dictionary members cannot be modified.
     67   */
     68 
     69  // Get the first codec from param.codecs.
     70  // Assert that param.codecs has at least one element
     71  function getFirstCodec(param) {
     72    const { codecs } = param;
     73    assert_greater_than(codecs.length, 0);
     74    return codecs[0];
     75  }
     76 
     77  function compareCodecParam(observed, expected) {
     78    assert_equals(observed.payloadType, expected.payloadType);
     79    assert_equals(observed.clockRate, expected.clockRate);
     80    assert_equals(observed.channels, expected.channels);
     81    // Comparisons of mime-type are case-insensitive (see
     82    // https://datatracker.ietf.org/doc/html/rfc2045#section-5.1)
     83    assert_equals(observed.mimeType.toLowerCase(), expected.mimeType.toLowerCase());
     84    // This is not ideal; Firefox does not store fmtp verbatim, it stores a
     85    // reserialiaztion of the parsed form. We would like to be able to test
     86    // the other fields without tripping over that. So, we only test
     87    // sdpFmtpLine if it is a property of |expected|.
     88    if (expected.hasOwnProperty('sdpFmtpLine')) {
     89      assert_equals(observed.sdpFmtpLine, expected.sdpFmtpLine);
     90    }
     91  }
     92 
     93  function compareCodecParams(observed, expected) {
     94    assert_equals(observed.length, expected.length);
     95    for (let i = 0; i < observed.length; ++i) {
     96      compareCodecParam(observed[i], expected[i]);
     97    }
     98  }
     99 
    100  // Does not support disregarding unsupported codecs in the SDP, so is not
    101  // suitable for all test-cases.
    102  function checkCodecsAgainstSDP(codecs, msection) {
    103    const rtpParameters = SDPUtils.parseRtpParameters(msection);
    104    const {kind} = SDPUtils.parseMLine(msection);
    105 
    106    assert_not_equals(codecs.length, 0);
    107    assert_equals(codecs.length, rtpParameters.codecs.length);
    108    for (let i = 0; i < codecs.length; ++i) {
    109      const observed = codecs[i];
    110      const fromSdp = rtpParameters.codecs[i];
    111      const expected = {
    112        payloadType: fromSdp.payloadType,
    113        clockRate: fromSdp.clockRate,
    114        mimeType: `${kind}/${fromSdp.name}`,
    115      };
    116      if (kind == 'audio') {
    117        expected.channels = fromSdp.channels;
    118      }
    119      const fmtps = SDPUtils.matchPrefixAndTrim(msection, `a=fmtp:${fromSdp.payloadType}`);
    120      if (fmtps.length == 1) {
    121        expected.sdpFmtpLine = fmtps[0];
    122      } else {
    123        // compareCodecParam will check if observed.sdpFmtpLine is undefined if we
    124        // set this, but will not perform any checks if we do not.
    125        expected.sdpFmtpLine = undefined;
    126      }
    127      compareCodecParam(observed, expected);
    128    }
    129  }
    130 
    131 ['audio', 'video'].forEach(kind => {
    132  /*
    133    5.2.  setParameters
    134      7.  If parameters.encodings.length is different from N, or if any parameter
    135          in the parameters argument, marked as a Read-only parameter, has a value
    136          that is different from the corresponding parameter value returned from
    137          sender.getParameters(), abort these steps and return a promise rejected
    138          with a newly created InvalidModificationError. Note that this also applies
    139          to transactionId.
    140   */
    141  promise_test(async t => {
    142    const pc = new RTCPeerConnection();
    143    t.add_cleanup(() => pc.close());
    144 
    145    const { sender } = pc.addTransceiver(kind);
    146    await doOfferAnswerExchange(t, pc);
    147 
    148    const param = sender.getParameters();
    149    validateSenderRtpParameters(param);
    150 
    151    const codec = getFirstCodec(param);
    152 
    153    if(codec.payloadType === undefined) {
    154      codec.payloadType = 8;
    155    } else {
    156      codec.payloadType += 1;
    157    }
    158 
    159    return promise_rejects_dom(t, 'InvalidModificationError',
    160      sender.setParameters(param));
    161  }, `setParameters() with codec.payloadType modified should reject with InvalidModificationError (${kind})`);
    162 
    163  promise_test(async t => {
    164    const pc = new RTCPeerConnection();
    165    t.add_cleanup(() => pc.close());
    166    const { sender } = pc.addTransceiver(kind);
    167    await doOfferAnswerExchange(t, pc);
    168    const param = sender.getParameters();
    169    validateSenderRtpParameters(param);
    170 
    171    const codec = getFirstCodec(param);
    172 
    173    if(codec.mimeType === undefined) {
    174      codec.mimeType = `${kind}/piedpiper`;
    175    } else {
    176      codec.mimeType = `${codec.mimeType}-modified`;
    177    }
    178 
    179    return promise_rejects_dom(t, 'InvalidModificationError',
    180      sender.setParameters(param));
    181  }, `setParameters() with codec.mimeType modified should reject with InvalidModificationError (${kind})`);
    182 
    183  promise_test(async t => {
    184    const pc = new RTCPeerConnection();
    185    t.add_cleanup(() => pc.close());
    186    const { sender } = pc.addTransceiver(kind);
    187    await doOfferAnswerExchange(t, pc);
    188    const param = sender.getParameters();
    189    validateSenderRtpParameters(param);
    190 
    191    const codec = getFirstCodec(param);
    192 
    193    if(codec.clockRate === undefined) {
    194      codec.clockRate = 8000;
    195    } else {
    196      codec.clockRate += 1;
    197    }
    198 
    199    return promise_rejects_dom(t, 'InvalidModificationError',
    200      sender.setParameters(param));
    201  }, `setParameters() with codec.clockRate modified should reject with InvalidModificationError (${kind})`);
    202 
    203  promise_test(async t => {
    204    const pc = new RTCPeerConnection();
    205    t.add_cleanup(() => pc.close());
    206    const { sender } = pc.addTransceiver(kind);
    207    await doOfferAnswerExchange(t, pc);
    208    const param = sender.getParameters();
    209    validateSenderRtpParameters(param);
    210 
    211    const codec = getFirstCodec(param);
    212 
    213    if(codec.channels === undefined) {
    214      codec.channels = 6;
    215    } else {
    216      codec.channels += 1;
    217    }
    218 
    219    return promise_rejects_dom(t, 'InvalidModificationError',
    220      sender.setParameters(param));
    221  }, `setParameters() with codec.channels modified should reject with InvalidModificationError (${kind})`);
    222 
    223  promise_test(async t => {
    224    const pc = new RTCPeerConnection();
    225    t.add_cleanup(() => pc.close());
    226    const { sender } = pc.addTransceiver(kind);
    227    await doOfferAnswerExchange(t, pc);
    228    const param = sender.getParameters();
    229    validateSenderRtpParameters(param);
    230 
    231    const codec = getFirstCodec(param);
    232 
    233    if(codec.sdpFmtpLine === undefined) {
    234      codec.sdpFmtpLine = 'a=fmtp:98 0-15';
    235    } else {
    236      codec.sdpFmtpLine = `${codec.sdpFmtpLine};foo=1`;
    237    }
    238 
    239    return promise_rejects_dom(t, 'InvalidModificationError',
    240      sender.setParameters(param));
    241  }, `setParameters() with codec.sdpFmtpLine modified should reject with InvalidModificationError (${kind})`);
    242 
    243  promise_test(async t => {
    244    const pc = new RTCPeerConnection();
    245    t.add_cleanup(() => pc.close());
    246    const { sender } = pc.addTransceiver(kind);
    247    await doOfferAnswerExchange(t, pc);
    248    const param = sender.getParameters();
    249    validateSenderRtpParameters(param);
    250 
    251    const { codecs } = param;
    252 
    253    codecs.push({
    254      payloadType: 2,
    255      mimeType: `${kind}/piedpiper`,
    256      clockRate: 1000,
    257      channels: 2
    258    });
    259 
    260    return promise_rejects_dom(t, 'InvalidModificationError',
    261      sender.setParameters(param));
    262  }, `setParameters() with new codecs inserted should reject with InvalidModificationError (${kind})`);
    263 
    264  promise_test(async t => {
    265    const pc = new RTCPeerConnection();
    266    t.add_cleanup(() => pc.close());
    267    const { sender } = pc.addTransceiver(kind);
    268    await doOfferAnswerExchange(t, pc);
    269    const param = sender.getParameters();
    270    validateSenderRtpParameters(param);
    271 
    272    const { codecs } = param;
    273    codecs.pop();
    274 
    275    return promise_rejects_dom(t, 'InvalidModificationError',
    276      sender.setParameters(param));
    277  }, `setParameters() with codecs removed should reject with InvalidModificationError (${kind})`);
    278 
    279  promise_test(async t => {
    280    const pc = new RTCPeerConnection();
    281    t.add_cleanup(() => pc.close());
    282    const { sender } = pc.addTransceiver(kind);
    283    await doOfferAnswerExchange(t, pc);
    284    const param = sender.getParameters();
    285    validateSenderRtpParameters(param);
    286 
    287    const { codecs } = param;
    288    codecs.reverse();
    289 
    290    return promise_rejects_dom(t, 'InvalidModificationError',
    291      sender.setParameters(param));
    292  }, `setParameters() with codecs reordered should reject with InvalidModificationError (${kind})`);
    293 
    294  promise_test(async t => {
    295    const pc = new RTCPeerConnection();
    296    t.add_cleanup(() => pc.close());
    297    const { sender } = pc.addTransceiver(kind);
    298    await doOfferAnswerExchange(t, pc);
    299    const param = sender.getParameters();
    300    validateSenderRtpParameters(param);
    301 
    302    delete param.codecs;
    303 
    304    return promise_rejects_dom(t, 'InvalidModificationError',
    305      sender.setParameters(param));
    306  }, `setParameters() with codecs undefined should reject with InvalidModificationError (${kind})`);
    307 
    308  promise_test(async t => {
    309    const pc1 = new RTCPeerConnection();
    310    t.add_cleanup(() => pc1.close());
    311    const pc2 = new RTCPeerConnection();
    312    t.add_cleanup(() => pc2.close());
    313 
    314    const sender1 = pc1.addTransceiver(kind).sender;
    315    let param = sender1.getParameters();
    316    assert_array_equals(param.codecs, [], 'No sender codecs in initial stable state');
    317 
    318    const offer = await pc1.createOffer();
    319    param = sender1.getParameters();
    320    assert_array_equals(param.codecs, [], 'No sender codecs in initial stable state (after createOffer)');
    321 
    322    await pc1.setLocalDescription(offer);
    323    param = sender1.getParameters();
    324    assert_array_equals(param.codecs, [], 'No sender codecs in initial have-local-offer');
    325 
    326    await pc2.setRemoteDescription(offer);
    327    const [sender2] = pc2.getSenders();
    328    param = sender2.getParameters();
    329    assert_array_equals(param.codecs, [], 'No sender codecs in initial have-remote-offer');
    330 
    331    const answer = await pc2.createAnswer();
    332    param = sender2.getParameters();
    333    assert_array_equals(param.codecs, [], 'No sender codecs in initial have-remote-offer (after createAnswer)');
    334  }, `RTCRtpSender.getParameters() should not have codecs before SDP negotiation completes (${kind})`);
    335 
    336  promise_test(async t => {
    337    const pc1 = new RTCPeerConnection();
    338    t.add_cleanup(() => pc1.close());
    339    const pc2 = new RTCPeerConnection();
    340    t.add_cleanup(() => pc2.close());
    341 
    342    const receiver1 = pc1.addTransceiver(kind).receiver;
    343    let param = receiver1.getParameters();
    344    assert_array_equals(param.codecs, [], 'No receiver codecs in initial stable state');
    345 
    346    const offer = await pc1.createOffer();
    347    param = receiver1.getParameters();
    348    assert_array_equals(param.codecs, [], 'No receiver codecs in initial stable state (after createOffer)');
    349 
    350    await pc1.setLocalDescription(offer);
    351    param = receiver1.getParameters();
    352    assert_array_equals(param.codecs, [], 'No receiver codecs in initial have-local-offer');
    353 
    354    await pc2.setRemoteDescription(offer);
    355    const [receiver2] = pc2.getReceivers();
    356    param = receiver2.getParameters();
    357    assert_array_equals(param.codecs, [], 'No receiver codecs in initial have-remote-offer');
    358 
    359    const answer = await pc2.createAnswer();
    360    param = receiver2.getParameters();
    361    assert_array_equals(param.codecs, [], 'No receiver codecs in initial have-remote-offer (after createAnswer)');
    362  }, `RTCRtpReceiver.getParameters() should not have codecs before SDP negotiation completes (${kind})`);
    363 
    364  promise_test(async t => {
    365    const pc1 = new RTCPeerConnection();
    366    t.add_cleanup(() => pc1.close());
    367    const pc2 = new RTCPeerConnection();
    368    t.add_cleanup(() => pc2.close());
    369 
    370    const sender1 = pc1.addTransceiver(kind).sender;
    371    await exchangeOfferAnswer(pc1, pc2);
    372    const [sender2] = pc2.getSenders();
    373 
    374    let param = sender1.getParameters();
    375    assert_array_field(param, 'codecs');
    376    assert_not_equals(param.codecs.length, 0);
    377    for (const codec of param.codecs) {
    378      validateCodecParameters(codec);
    379    }
    380 
    381    param = sender2.getParameters();
    382    assert_array_field(param, 'codecs');
    383    assert_not_equals(param.codecs.length, 0);
    384    for (const codec of param.codecs) {
    385      validateCodecParameters(codec);
    386    }
    387  }, `RTCRtpSender.getParameters() should have codecs after negotiation (${kind})`);
    388 
    389 promise_test(async t => {
    390    const pc1 = new RTCPeerConnection();
    391    t.add_cleanup(() => pc1.close());
    392    const pc2 = new RTCPeerConnection();
    393    t.add_cleanup(() => pc2.close());
    394 
    395    const receiver1 = pc1.addTransceiver(kind).receiver;
    396    await exchangeOfferAnswer(pc1, pc2);
    397    const [receiver2] = pc2.getReceivers();
    398 
    399    let param = receiver1.getParameters();
    400    assert_array_field(param, 'codecs');
    401    assert_not_equals(param.codecs.length, 0);
    402    for (const codec of param.codecs) {
    403      validateCodecParameters(codec);
    404    }
    405 
    406    param = receiver2.getParameters();
    407    assert_array_field(param, 'codecs');
    408    assert_not_equals(param.codecs.length, 0);
    409    for (const codec of param.codecs) {
    410      validateCodecParameters(codec);
    411    }
    412  }, `RTCRtpReceiver.getParameters() should have codecs after negotiation (${kind})`);
    413 
    414  promise_test(async t => {
    415    const pc = new RTCPeerConnection();
    416    t.add_cleanup(() => pc.close());
    417 
    418    const { sender } = pc.addTransceiver(kind);
    419    await doOfferAnswerExchange(t, pc);
    420 
    421    const {codecs} = pc.getReceivers()[0].getParameters();
    422    const sections = SDPUtils.splitSections(pc.localDescription.sdp);
    423    checkCodecsAgainstSDP(codecs, sections[1]);
    424  }, `RTCRtpReceiver.getParameters() codecs should match local SDP (${kind}, offerer)`);
    425 
    426  promise_test(async t => {
    427    const pc1 = new RTCPeerConnection();
    428    t.add_cleanup(() => pc1.close());
    429    const pc2 = new RTCPeerConnection();
    430    t.add_cleanup(() => pc2.close());
    431 
    432    pc1.addTransceiver(kind);
    433    await exchangeOfferAnswer(pc1, pc2);
    434 
    435    const {codecs} = pc2.getReceivers()[0].getParameters();
    436    const sections = SDPUtils.splitSections(pc2.localDescription.sdp);
    437    checkCodecsAgainstSDP(codecs, sections[1]);
    438  }, `RTCRtpReceiver.getParameters() codecs should match local SDP (${kind}, answerer)`);
    439 });
    440 
    441  // SDP with unusual payload types and fmtp, and an unknown codec
    442  const audioSdp = `v=0
    443 o=- 166855176514521964 2 IN IP4 127.0.0.1
    444 s=-
    445 t=0 0
    446 m=audio 9 UDP/TLS/RTP/SAVPF 121 111 0 101
    447 c=IN IP4 0.0.0.0
    448 a=rtcp:9 IN IP4 0.0.0.0
    449 a=ice-ufrag:foobarba
    450 a=ice-pwd:foobarba
    451 a=fingerprint:sha-256 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
    452 a=setup:passive
    453 a=mid:mid1
    454 a=sendrecv
    455 a=rtcp-rsize
    456 a=rtpmap:101 telephone-event/8000/1
    457 a=rtpmap:121 flarglblurp/8000/2
    458 a=rtpmap:111 opus/48000/2
    459 a=fmtp:111 maxaveragebitrate=20001;unknownparam=foo
    460 `;
    461 
    462  // SDP with unusual payload types and fmtp, and an unknown codec
    463  const videoSdp = `v=0
    464 o=- 1878890426675213188 2 IN IP4 127.0.0.1
    465 s=-
    466 t=0 0
    467 m=video 9 UDP/TLS/RTP/SAVPF 116 117 120 124 119 123 122 121 118
    468 c=IN IP4 0.0.0.0
    469 a=sendrecv
    470 a=fmtp:117 apt=116
    471 a=fmtp:120 max-fs=12277;max-fr=50;unknownparam=foo
    472 a=fmtp:124 apt=120
    473 a=fmtp:119 max-fs=12266;max-fr=40
    474 a=fmtp:123 apt=119
    475 a=fmtp:118 apt=121
    476 a=ice-pwd:60840251a559417c253d68478b0020fb
    477 a=ice-ufrag:741347dd
    478 a=fingerprint:sha-256 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
    479 a=setup:passive
    480 a=mid:mid1
    481 a=rtcp-mux
    482 a=rtcp-rsize
    483 a=rtpmap:116 flarglblurp/90000
    484 a=rtpmap:117 rtx/90000
    485 a=rtpmap:120 VP9/90000
    486 a=rtpmap:124 rtx/90000
    487 a=rtpmap:119 VP8/90000
    488 a=rtpmap:123 rtx/90000
    489 a=rtpmap:122 ulpfec/90000
    490 a=rtpmap:121 red/90000
    491 a=rtpmap:118 rtx/90000
    492 `;
    493 
    494  const remoteSdpParamsTests = [
    495    {
    496      description: 'audio, no fmtp checks',
    497      kind: 'audio',
    498      sdp: audioSdp,
    499      expectedCodecs: [
    500        {payloadType: 111, clockRate: 48000, channels: 2, mimeType: 'audio/opus'},
    501        {payloadType: 0, clockRate: 8000, channels: 1, mimeType: 'audio/pcmu'},
    502        {payloadType: 101, clockRate: 8000, channels: 1, mimeType: 'audio/telephone-event'},
    503      ]
    504    },
    505 
    506    {
    507      description: 'audio, with fmtp checks',
    508      kind: 'audio',
    509      sdp: audioSdp,
    510      expectedCodecs: [
    511        {payloadType: 111, clockRate: 48000, channels: 2, mimeType: 'audio/opus', sdpFmtpLine: 'maxaveragebitrate=20001;unknownparam=foo'},
    512        {payloadType: 0, clockRate: 8000, channels: 1, mimeType: 'audio/pcmu', sdpFmtpLine: undefined},
    513        {payloadType: 101, clockRate: 8000, channels: 1, mimeType: 'audio/telephone-event', sdpFmtpLine: undefined},
    514      ]
    515    },
    516 
    517    {
    518      description: 'video, minimal fmtp checks',
    519      kind: 'video',
    520      sdp: videoSdp,
    521      expectedCodecs: [
    522        {payloadType: 120, clockRate: 90000, mimeType: 'video/vp9'},
    523        {payloadType: 124, clockRate: 90000, mimeType: 'video/rtx', sdpFmtpLine: 'apt=120'},
    524        {payloadType: 119, clockRate: 90000, mimeType: 'video/vp8'},
    525        {payloadType: 123, clockRate: 90000, mimeType: 'video/rtx', sdpFmtpLine: 'apt=119'},
    526        {payloadType: 122, clockRate: 90000, mimeType: 'video/ulpfec'},
    527        {payloadType: 121, clockRate: 90000, mimeType: 'video/red'},
    528        {payloadType: 118, clockRate: 90000, mimeType: 'video/rtx', sdpFmtpLine: 'apt=121'},
    529      ]
    530    },
    531 
    532    {
    533      description: 'video, with fmtp checks',
    534      kind: 'video',
    535      sdp: videoSdp,
    536      expectedCodecs: [
    537        {payloadType: 120, clockRate: 90000, mimeType: 'video/vp9', sdpFmtpLine: 'max-fs=12277;max-fr=50;unknownparam=foo'},
    538        {payloadType: 124, clockRate: 90000, mimeType: 'video/rtx', sdpFmtpLine: 'apt=120'},
    539        {payloadType: 119, clockRate: 90000, mimeType: 'video/vp8', sdpFmtpLine: 'max-fs=12266;max-fr=40'},
    540        {payloadType: 123, clockRate: 90000, mimeType: 'video/rtx', sdpFmtpLine: 'apt=119'},
    541        {payloadType: 122, clockRate: 90000, mimeType: 'video/ulpfec', sdpFmtpLine: undefined},
    542        {payloadType: 121, clockRate: 90000, mimeType: 'video/red', sdpFmtpLine: undefined},
    543        {payloadType: 118, clockRate: 90000, mimeType: 'video/rtx', sdpFmtpLine: 'apt=121'},
    544      ]
    545    },
    546  ];
    547 
    548  remoteSdpParamsTests.forEach(test => {
    549    promise_test(async t => {
    550      const pc = new RTCPeerConnection();
    551      t.add_cleanup(() => pc.close());
    552 
    553      pc.addTransceiver(test.kind, { direction: 'sendrecv'});
    554      await pc.setLocalDescription();
    555      const {sender, mid} = pc.getTransceivers()[0];
    556      await pc.setRemoteDescription({sdp: test.sdp.replace('mid1', mid), type: 'answer'});
    557      const {codecs} = sender.getParameters();
    558      compareCodecParams(codecs, test.expectedCodecs);
    559    }, `RTCRtpSender.getParameters() codecs should match remote SDP (${test.description}, offerer)`);
    560 
    561    promise_test(async t => {
    562      const pc = new RTCPeerConnection();
    563      t.add_cleanup(() => pc.close());
    564 
    565      await pc.setRemoteDescription({sdp: test.sdp, type: 'offer'});
    566      await pc.setLocalDescription();
    567      const {codecs} = pc.getSenders()[0].getParameters();
    568      compareCodecParams(codecs, test.expectedCodecs);
    569    }, `RTCRtpSender.getParameters() codecs should match remote SDP (${test.description}, answerer)`);
    570  });
    571 
    572 </script>