tor-browser

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

RTCPeerConnection-insertable-streams.js (9149B)


      1 function areArrayBuffersEqual(buffer1, buffer2)
      2 {
      3  if (buffer1.byteLength !== buffer2.byteLength) {
      4    return false;
      5  }
      6  let array1 = new Int8Array(buffer1);
      7  var array2 = new Int8Array(buffer2);
      8  for (let i = 0 ; i < buffer1.byteLength ; ++i) {
      9    if (array1[i] !== array2[i]) {
     10      return false;
     11    }
     12  }
     13  return true;
     14 }
     15 
     16 function areArraysEqual(a1, a2) {
     17  if (a1 === a1)
     18    return true;
     19  if (a1.length != a2.length)
     20    return false;
     21  for (let i = 0; i < a1.length; i++) {
     22    if (a1[i] != a2[i])
     23      return false;
     24  }
     25  return true;
     26 }
     27 
     28 function areMetadataEqual(metadata1, metadata2, type) {
     29  return metadata1.synchronizationSource === metadata2.synchronizationSource &&
     30          metadata1.payloadType == metadata2.payloadType &&
     31          areArraysEqual(
     32              metadata1.contributingSources, metadata2.contributingSources) &&
     33          metadata1.captureTime == metadata2.captureTime &&
     34          metadata1.frameId === metadata2.frameId &&
     35          areArraysEqual(metadata1.dependencies, metadata2.dependencies) &&
     36          metadata1.spatialIndex === metadata2.spatialIndex &&
     37          metadata1.temporalIndex === metadata2.temporalIndex &&
     38          // Width and height are reported only for key frames on the receiver
     39          // side.
     40          type == 'key' ?
     41      metadata1.width === metadata2.width &&
     42          metadata1.height === metadata2.height :
     43      true;
     44 }
     45 
     46 function areFrameInfosEqual(frame1, frame2) {
     47  return frame1.timestamp === frame2.timestamp &&
     48         frame1.type === frame2.type &&
     49         areMetadataEqual(frame1.getMetadata(), frame2.getMetadata(), frame1.type) &&
     50         areArrayBuffersEqual(frame1.data, frame2.data);
     51 }
     52 
     53 function containsVideoMetadata(metadata) {
     54  return metadata.synchronizationSource !== undefined &&
     55         metadata.width !== undefined &&
     56         metadata.height !== undefined &&
     57         metadata.spatialIndex !== undefined &&
     58         metadata.temporalIndex !== undefined &&
     59         metadata.dependencies !== undefined;
     60 }
     61 
     62 function enableExtension(sdp, extension) {
     63  if (sdp.indexOf(extension) !== -1)
     64    return sdp;
     65 
     66  const extensionIds = sdp.trim().split('\n')
     67    .map(line => line.trim())
     68    .filter(line => line.startsWith('a=extmap:'))
     69    .map(line => line.split(' ')[0].substr(9))
     70    .map(id => parseInt(id, 10))
     71    .sort((a, b) => a - b);
     72  for (let newId = 1; newId <= 15; newId++) {
     73    if (!extensionIds.includes(newId)) {
     74      return sdp += 'a=extmap:' + newId + ' ' + extension + '\r\n';
     75    }
     76  }
     77  if (sdp.indexOf('a=extmap-allow-mixed') !== -1) { // Pick the next highest one.
     78    const newId = extensionIds[extensionIds.length - 1] + 1;
     79    return sdp += 'a=extmap:' + newId + ' ' + extension + '\r\n';
     80  }
     81  throw 'Could not find free extension id to use for ' + extension;
     82 }
     83 
     84 const GFD_V00_EXTENSION =
     85    'http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-00';
     86 const ABS_V00_EXTENSION =
     87    'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time';
     88 
     89 async function exchangeOfferAnswer(pc1, pc2) {
     90  const offer = await pc1.createOffer();
     91  // Munge the SDP to enable the GFD and ACT extension in order to get correct
     92  // metadata.
     93  const sdpABS = enableExtension(offer.sdp, ABS_V00_EXTENSION);
     94  const sdpGFD = enableExtension(sdpABS, GFD_V00_EXTENSION);
     95  await pc1.setLocalDescription({type: offer.type, sdp: sdpGFD});
     96  // Munge the SDP to disable bandwidth probing via RTX.
     97  // TODO(crbug.com/1066819): remove this hack when we do not receive duplicates from RTX
     98  // anymore.
     99  const sdpRTX = sdpGFD.replace(new RegExp('rtx', 'g'), 'invalid');
    100  await pc2.setRemoteDescription({type: 'offer', sdp: sdpRTX});
    101 
    102  const answer = await pc2.createAnswer();
    103  await pc2.setLocalDescription(answer);
    104  await pc1.setRemoteDescription(answer);
    105 }
    106 
    107 async function exchangeOfferAnswerReverse(pc1, pc2, encodedStreamsCallback) {
    108  const offer = await pc2.createOffer({offerToReceiveAudio: true, offerToReceiveVideo: true});
    109  if (encodedStreamsCallback) {
    110    // RTCRtpReceivers will have been created during the above createOffer call, so if the caller
    111    // wants to createEncodedStreams synchronously after creation to ensure all frames pass
    112    // through the transform, it will have to be done now.
    113    encodedStreamsCallback(
    114      pc2.getReceivers().map(r => {
    115        return {kind: r.track.kind, streams: r.createEncodedStreams()};
    116      }));
    117  }
    118 
    119  // Munge the SDP to enable the GFD extension in order to get correct metadata.
    120  const sdpABS = enableExtension(offer.sdp, ABS_V00_EXTENSION);
    121  const sdpGFD = enableExtension(sdpABS, GFD_V00_EXTENSION);
    122  // Munge the SDP to disable bandwidth probing via RTX.
    123  // TODO(crbug.com/1066819): remove this hack when we do not receive duplicates from RTX
    124  // anymore.
    125  const sdpRTX = sdpGFD.replace(new RegExp('rtx', 'g'), 'invalid');
    126  await pc1.setRemoteDescription({type: 'offer', sdp: sdpRTX});
    127  await pc2.setLocalDescription({type: 'offer', sdp: sdpGFD});
    128 
    129  const answer = await pc1.createAnswer();
    130  await pc2.setRemoteDescription(answer);
    131  await pc1.setLocalDescription(answer);
    132 }
    133 
    134 function createFrameDescriptor(videoFrame) {
    135  const kMaxSpatialLayers = 8;
    136  const kMaxTemporalLayers = 8;
    137  const kMaxNumFrameDependencies = 8;
    138 
    139  const metadata = videoFrame.getMetadata();
    140  let frameDescriptor = {
    141    beginningOfSubFrame: true,
    142    endOfSubframe: false,
    143    frameId: metadata.frameId & 0xFFFF,
    144    spatialLayers: 1 << metadata.spatialIndex,
    145    temporalLayer: metadata.temporalLayer,
    146    frameDependenciesDiffs: [],
    147    width: 0,
    148    height: 0
    149  };
    150 
    151  for (const dependency of metadata.dependencies) {
    152    frameDescriptor.frameDependenciesDiffs.push(metadata.frameId - dependency);
    153  }
    154  if (metadata.dependencies.length == 0) {
    155    frameDescriptor.width = metadata.width;
    156    frameDescriptor.height = metadata.height;
    157  }
    158  return frameDescriptor;
    159 }
    160 
    161 function additionalDataSize(descriptor) {
    162  if (!descriptor.beginningOfSubFrame) {
    163    return 1;
    164  }
    165 
    166  let size = 4;
    167  for (const fdiff of descriptor.frameDependenciesDiffs) {
    168    size += (fdiff >= (1 << 6)) ? 2 : 1;
    169  }
    170  if (descriptor.beginningOfSubFrame &&
    171      descriptor.frameDependenciesDiffs.length == 0 &&
    172      descriptor.width > 0 &&
    173      descriptor.height > 0) {
    174    size += 4;
    175  }
    176 
    177  return size;
    178 }
    179 
    180 // Compute the buffer reported in the additionalData field using the metadata
    181 // provided by a video frame.
    182 // Based on the webrtc::RtpDescriptorAuthentication() C++ function at
    183 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/modules/rtp_rtcp/source/rtp_descriptor_authentication.cc
    184 function computeAdditionalData(videoFrame) {
    185  const kMaxSpatialLayers = 8;
    186  const kMaxTemporalLayers = 8;
    187  const kMaxNumFrameDependencies = 8;
    188 
    189  const metadata = videoFrame.getMetadata();
    190  if (metadata.spatialIndex < 0 ||
    191      metadata.temporalIndex < 0 ||
    192      metadata.spatialIndex >= kMaxSpatialLayers ||
    193      metadata.temporalIndex >= kMaxTemporalLayers ||
    194      metadata.dependencies.length > kMaxNumFrameDependencies) {
    195    return new ArrayBuffer(0);
    196  }
    197 
    198  const descriptor = createFrameDescriptor(videoFrame);
    199  const size = additionalDataSize(descriptor);
    200  const additionalData = new ArrayBuffer(size);
    201  const data = new Uint8Array(additionalData);
    202 
    203  const kFlagBeginOfSubframe = 0x80;
    204  const kFlagEndOfSubframe = 0x40;
    205  const kFlagFirstSubframeV00 = 0x20;
    206  const kFlagLastSubframeV00 = 0x10;
    207 
    208  const kFlagDependencies = 0x08;
    209  const kFlagMoreDependencies = 0x01;
    210  const kFlageXtendedOffset = 0x02;
    211 
    212  let baseHeader =
    213    (descriptor.beginningOfSubFrame ? kFlagBeginOfSubframe : 0) |
    214    (descriptor.endOfSubFrame ? kFlagEndOfSubframe : 0);
    215  baseHeader |= kFlagFirstSubframeV00;
    216  baseHeader |= kFlagLastSubframeV00;
    217 
    218  if (!descriptor.beginningOfSubFrame) {
    219    data[0] = baseHeader;
    220    return additionalData;
    221  }
    222 
    223  data[0] =
    224      baseHeader |
    225      (descriptor.frameDependenciesDiffs.length == 0 ? 0 : kFlagDependencies) |
    226      descriptor.temporalLayer;
    227  data[1] = descriptor.spatialLayers;
    228  data[2] = descriptor.frameId & 0xFF;
    229  data[3] = descriptor.frameId >> 8;
    230 
    231  const fdiffs = descriptor.frameDependenciesDiffs;
    232  let offset = 4;
    233  if (descriptor.beginningOfSubFrame &&
    234      fdiffs.length == 0 &&
    235      descriptor.width > 0 &&
    236      descriptor.height > 0) {
    237    data[offset++] = (descriptor.width >> 8);
    238    data[offset++] = (descriptor.width & 0xFF);
    239    data[offset++] = (descriptor.height >> 8);
    240    data[offset++] = (descriptor.height & 0xFF);
    241  }
    242  for (let i = 0; i < fdiffs.length; i++) {
    243    const extended = fdiffs[i] >= (1 << 6);
    244    const more = i < fdiffs.length - 1;
    245    data[offset++] = ((fdiffs[i] & 0x3f) << 2) |
    246                     (extended ? kFlageXtendedOffset : 0) |
    247                     (more ? kFlagMoreDependencies : 0);
    248    if (extended) {
    249      data[offset++] = fdiffs[i] >> 6;
    250    }
    251  }
    252  return additionalData;
    253 }
    254 
    255 function verifyNonstandardAdditionalDataIfPresent(videoFrame) {
    256  if (videoFrame.additionalData === undefined)
    257    return;
    258 
    259  const computedData = computeAdditionalData(videoFrame);
    260  assert_true(areArrayBuffersEqual(videoFrame.additionalData, computedData));
    261 }