in src/redundantaudioencoder/RedundantAudioEncoder.ts [438:537]
private encode(primaryTimestamp: number, primaryPayload: ArrayBuffer): ArrayBuffer | null {
const primaryPayloadSize = primaryPayload.byteLength;
// Payload size needs to be valid.
if (
primaryPayloadSize === 0 ||
primaryPayloadSize >= this.maxRedPacketSizeBytes ||
primaryPayloadSize >= this.maxAudioPayloadSizeBytes
) {
return null;
}
const numRedundantEncodings = this.numRedundantEncodings;
let headerSizeBytes = this.redLastHeaderSizeBytes;
let payloadSizeBytes = primaryPayloadSize;
let bytesAvailable = this.maxAudioPayloadSizeBytes - primaryPayloadSize - headerSizeBytes;
const redundantEncodingTimestamps: Array<number> = new Array();
const redundantEncodingPayloads: Array<ArrayBuffer> = new Array();
// If redundancy is disabled then only send the primary payload
if (this.redundancyEnabled) {
// Determine how much redundancy we can fit into our packet
let redundantTimestamp = this.uint32WrapAround(
primaryTimestamp - this.redPacketizationTime * this.redPacketDistance
);
for (let i = 0; i < numRedundantEncodings; i++) {
// Do not add redundant encodings that are beyond the maximum timestamp offset.
if (
this.uint32WrapAround(primaryTimestamp - redundantTimestamp) >= this.maxRedTimestampOffset
) {
break;
}
let findTimestamp = redundantTimestamp;
let encoding = this.encodingHistory.find(e => e.timestamp === findTimestamp);
if (!encoding) {
// If not found or not important then look for the previous packet.
// The current packet may have included FEC for the previous, so just
// use the previous packet instead provided that it has voice activity.
findTimestamp = this.uint32WrapAround(redundantTimestamp - this.redPacketizationTime);
encoding = this.encodingHistory.find(e => e.timestamp === findTimestamp);
}
if (encoding) {
const redundantEncodingSizeBytes = encoding.payload.byteLength;
// Only add redundancy if there are enough bytes available.
if (bytesAvailable < this.redHeaderSizeBytes + redundantEncodingSizeBytes) break;
bytesAvailable -= this.redHeaderSizeBytes + redundantEncodingSizeBytes;
headerSizeBytes += this.redHeaderSizeBytes;
payloadSizeBytes += redundantEncodingSizeBytes;
redundantEncodingTimestamps.unshift(encoding.timestamp);
redundantEncodingPayloads.unshift(encoding.payload);
}
redundantTimestamp -= this.redPacketizationTime * this.redPacketDistance;
redundantTimestamp = this.uint32WrapAround(redundantTimestamp);
}
}
const redPayloadBuffer = new ArrayBuffer(headerSizeBytes + payloadSizeBytes);
const redPayloadView = new DataView(redPayloadBuffer);
// Add redundant encoding header(s) to new buffer
let redPayloadOffset = 0;
for (let i = 0; i < redundantEncodingTimestamps.length; i++) {
const timestampDelta = primaryTimestamp - redundantEncodingTimestamps[i];
redPayloadView.setUint8(redPayloadOffset, this.opusPayloadType | 0x80);
redPayloadView.setUint16(
redPayloadOffset + 1,
(timestampDelta << 2) | (redundantEncodingPayloads[i].byteLength >> 8)
);
redPayloadView.setUint8(redPayloadOffset + 3, redundantEncodingPayloads[i].byteLength & 0xff);
redPayloadOffset += this.redHeaderSizeBytes;
}
// Add primary encoding header to new buffer
redPayloadView.setUint8(redPayloadOffset, this.opusPayloadType);
redPayloadOffset += this.redLastHeaderSizeBytes;
// Add redundant payload(s) to new buffer
const redPayloadArray = new Uint8Array(redPayloadBuffer);
for (let i = 0; i < redundantEncodingPayloads.length; i++) {
redPayloadArray.set(new Uint8Array(redundantEncodingPayloads[i]), redPayloadOffset);
redPayloadOffset += redundantEncodingPayloads[i].byteLength;
}
// Add primary payload to new buffer
redPayloadArray.set(new Uint8Array(primaryPayload), redPayloadOffset);
redPayloadOffset += primaryPayload.byteLength;
/* istanbul ignore next */
// Sanity check that we got the expected total payload size.
if (redPayloadOffset !== headerSizeBytes + payloadSizeBytes) return null;
this.updateEncodingHistory(primaryTimestamp, primaryPayload);
return redPayloadBuffer;
}