private splitEncodings()

in src/redundantaudioencoder/RedundantAudioEncoder.ts [294:433]


  private splitEncodings(
    primaryTimestamp: number,
    frame: ArrayBuffer,
    getFecInfo: boolean = false,
    primarySequenceNumber: number = undefined
  ): RedundantAudioEncoder.Encoding[] | null {
    // process RED headers (according to RFC 2198)
    //   0                   1                   2                   3
    //   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //  |F|   block PT  |  timestamp offset         |   block length    |
    //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //
    // last header
    //   0 1 2 3 4 5 6 7
    //  +-+-+-+-+-+-+-+-+
    //  |0|   Block PT  |
    //  +-+-+-+-+-+-+-+-+

    const payload = new DataView(frame);
    let payloadSizeBytes = payload.byteLength;
    let totalPayloadSizeBytes = 0;
    let totalHeaderSizeBytes = 0;
    let primaryPayloadSizeBytes = 0;
    let payloadOffset = 0;
    let gotLastBlock = false;
    const encodings = new Array<RedundantAudioEncoder.Encoding>();
    const redundantEncodingBlockLengths = new Array();
    const redundantEncodingTimestamps = new Array();

    while (payloadSizeBytes > 0) {
      gotLastBlock = (payload.getUint8(payloadOffset) & 0x80) === 0;
      if (gotLastBlock) {
        // Bits 1 through 7 are payload type
        const payloadType = payload.getUint8(payloadOffset) & 0x7f;

        // Unexpected payload type. This is a bad packet.
        if (payloadType !== this.opusPayloadType) {
          return null;
        }

        totalPayloadSizeBytes += this.redLastHeaderSizeBytes;
        totalHeaderSizeBytes += this.redLastHeaderSizeBytes;

        // Accumulated block lengths are equal to or larger than the buffer, which means there is no primary block. This
        // is a bad packet.
        if (totalPayloadSizeBytes >= payload.byteLength) {
          return null;
        }

        primaryPayloadSizeBytes = payload.byteLength - totalPayloadSizeBytes;
        break;
      } else {
        if (payloadSizeBytes < this.redHeaderSizeBytes) {
          return null;
        }

        // Bits 22 through 31 are payload length
        const blockLength =
          ((payload.getUint8(payloadOffset + 2) & 0x03) << 8) + payload.getUint8(payloadOffset + 3);
        redundantEncodingBlockLengths.push(blockLength);
        const timestampOffset = payload.getUint16(payloadOffset + 1) >> 2;
        const timestamp = primaryTimestamp - timestampOffset;
        redundantEncodingTimestamps.push(timestamp);
        totalPayloadSizeBytes += blockLength + this.redHeaderSizeBytes;
        totalHeaderSizeBytes += this.redHeaderSizeBytes;
        payloadOffset += this.redHeaderSizeBytes;
        payloadSizeBytes -= this.redHeaderSizeBytes;
      }
    }

    // The last block was never found. The packet we received
    // does not have a good RED payload.
    if (!gotLastBlock) {
      // Note that sequence numbers only exist for
      // incoming audio frames.
      if (primarySequenceNumber !== undefined) {
        // This could be a possible padding packet used
        // for BWE with a good sequence number.
        // Create a dummy encoding to make sure loss values
        // are calculated correctly by consuming sequence number.
        // Note that for the receive side, we process packets only
        // for loss/recovery calculations and forward the original
        // packet without changing it even in the error case.
        encodings.push({
          payload: frame,
          isRedundant: false,
          seq: primarySequenceNumber,
        });
        return encodings;
      }
      // This is a bad packet.
      return null;
    }

    let redundantPayloadOffset = totalHeaderSizeBytes;
    for (let i = 0; i < redundantEncodingTimestamps.length; i++) {
      const redundantPayloadBuffer = new ArrayBuffer(redundantEncodingBlockLengths[i]);
      const redundantPayloadArray = new Uint8Array(redundantPayloadBuffer);
      redundantPayloadArray.set(
        new Uint8Array(payload.buffer, redundantPayloadOffset, redundantEncodingBlockLengths[i]),
        0
      );
      const encoding: RedundantAudioEncoder.Encoding = {
        timestamp: redundantEncodingTimestamps[i],
        payload: redundantPayloadBuffer,
        isRedundant: true,
      };
      if (getFecInfo) {
        encoding.hasFec = this.opusPacketHasFec(
          new DataView(redundantPayloadBuffer),
          redundantPayloadBuffer.byteLength
        );
      }
      encodings.push(encoding);
      redundantPayloadOffset += redundantEncodingBlockLengths[i];
    }

    const primaryPayloadOffset = payload.byteLength - primaryPayloadSizeBytes;
    const primaryPayloadBuffer = new ArrayBuffer(primaryPayloadSizeBytes);
    const primaryArray = new Uint8Array(primaryPayloadBuffer);
    primaryArray.set(
      new Uint8Array(payload.buffer, primaryPayloadOffset, primaryPayloadSizeBytes),
      0
    );
    const encoding: RedundantAudioEncoder.Encoding = {
      timestamp: primaryTimestamp,
      payload: primaryPayloadBuffer,
      isRedundant: false,
      seq: primarySequenceNumber,
    };
    if (getFecInfo) {
      encoding.hasFec = this.opusPacketHasFec(
        new DataView(primaryPayloadBuffer),
        primaryPayloadBuffer.byteLength
      );
    }
    encodings.push(encoding);
    return encodings;
  }