Skip to content

Commit

Permalink
RTCP Google Transport Congestion Feedback
Browse files Browse the repository at this point in the history
(WIP)
  • Loading branch information
ibc committed Sep 6, 2023
1 parent 4404382 commit 967903f
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 1 deletion.
11 changes: 10 additions & 1 deletion src/packets/RTCP/FeedbackPacket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ export enum RtpFeedbackMessageType
/**
* Explicit Congestion Notification (ECN).
*/
ECN = 8
ECN = 8,
/**
* Google Transport-wide Congestion Control.
*/
TCC = 15
}

/**
Expand Down Expand Up @@ -109,6 +113,11 @@ function messageTypeToString(
return 'Explicit Congestion Notification (ECN)';
}

case RtpFeedbackMessageType.TCC:
{
return 'Google Transport-wide Congestion Control';
}

case PsFeedbackMessageType.PLI:
{
return 'Picture Loss Indication';
Expand Down
228 changes: 228 additions & 0 deletions src/packets/RTCP/TccPacket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { RtcpPacketType } from './RtcpPacket';
import {
FeedbackPacket,
RtpFeedbackMessageType,
FeedbackPacketDump,
FIXED_HEADER_LENGTH
} from './FeedbackPacket';

const TCC_MIN_PACKET_LENGTH = FIXED_HEADER_LENGTH + 8;
const MAX_MISSING_PACKETS = (1 << 13) - 1;
const MAX_PACKET_STATUS_COUNT = (1 << 16) - 1;
const MAX_PACKET_DELTA = 0x7FFF;

/**
* RTCP TCC packet info dump.
*
* @category RTCP
*/
export type TccPacketDump = FeedbackPacketDump &
{
baseSequenceNumber: number;
packetStatusCount: number;
referenceTime: number;
feedbackPacketCount: number;
packetChunks: number[];
recvDeltas: number[];
};

/**
* RTCP TCC packet (RTCP Transport Layer Feedback).
*
* ```text
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |V=2|P| FMT=15 | PT=205 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of packet sender |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of media source |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | base sequence number | packet status count |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | reference time | fb pkt. count |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | packet chunk | packet chunk |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* . .
* . .
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | packet chunk | recv delta | recv delta |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* . .
* . .
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | recv delta | recv delta | zero padding |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ```
*
* @category RTCP
*
* @see
* - [draft-holmer-rmcat-transport-wide-cc-extensions-01 section 3.1](https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1)
*/
export class TccPacket extends FeedbackPacket
{
#items: { pid: number; bitmask: number }[] = [];

/**
* @param view - If given it will be parsed. Otherwise an empty RTCP TCC
* packet will be created.
*
* @throws
* - If given `view` does not contain a valid RTCP TCC packet.
*/
constructor(view?: DataView)
{
super(RtcpPacketType.RTPFB, RtpFeedbackMessageType.TCC, view);

if (!this.view)
{
this.view = new DataView(new ArrayBuffer(FIXED_HEADER_LENGTH));

// Write version, packet type and feedback message type.
this.writeFixedHeader();

return;
}

// Position relative to the DataView byte offset.
let pos = 0;

// Move to items.
pos += FIXED_HEADER_LENGTH;

while (pos < this.view.byteLength - this.padding)
{
const pid = this.view.getUint16(pos);

pos += 2;

const bitmask = this.view.getUint16(pos);

pos += 2;

this.#items.push({ pid, bitmask });
}

pos += this.padding;

// Ensure that view length and parsed length match.
if (pos !== this.view.byteLength)
{
throw new RangeError(
`parsed length (${pos} bytes) does not match view length (${this.view.byteLength} bytes)`
);
}
}

/**
* Dump RTCP TCC packet info.
*/
dump(): TccPacketDump
{
return {
...super.dump(),
baseSequenceNumber : this.getBaseSequenceNumber();

Check failure on line 127 in src/packets/RTCP/TccPacket.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-22.04, 16)

',' expected.
packetStatusCount : this.getPacketStatusCount();

Check failure on line 128 in src/packets/RTCP/TccPacket.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-22.04, 16)

',' expected.
referenceTime : this.getReferenceTime();

Check failure on line 129 in src/packets/RTCP/TccPacket.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-22.04, 16)

',' expected.
feedbackPacketCount : this.getFeedbackPacketCount();

Check failure on line 130 in src/packets/RTCP/TccPacket.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-22.04, 16)

',' expected.
};
}

/**
* @inheritDoc
*/
getByteLength(): number
{
if (!this.needsSerialization())
{
return this.view.byteLength;
}

const packetLength =
FIXED_HEADER_LENGTH +
(this.#items.length * 4) +
this.padding;

return packetLength;
}

/**
* @inheritDoc
*/
serialize(buffer?: ArrayBuffer, byteOffset?: number): void
{
const view = this.serializeBase(buffer, byteOffset);

// Position relative to the DataView byte offset.
let pos = 0;

// Move to items.
pos += FIXED_HEADER_LENGTH;

// Write items.
for (const { pid, bitmask } of this.#items)
{
view.setUint16(pos, pid);

pos += 2;

view.setUint16(pos, bitmask);

pos += 2;
}

pos += this.padding;

// Assert that current position is equal than new buffer length.
if (pos !== view.byteLength)
{
throw new RangeError(
`filled length (${pos} bytes) is different than the available buffer size (${view.byteLength} bytes)`
);
}

// Update DataView.
this.view = view;

this.setSerializationNeeded(false);
}

/**
* @inheritDoc
*/
clone(
buffer?: ArrayBuffer,
byteOffset?: number,
serializationBuffer?: ArrayBuffer,
serializationByteOffset?: number
): TccPacket
{
const view = this.cloneInternal(
buffer,
byteOffset,
serializationBuffer,
serializationByteOffset
);

return new TccPacket(view);
}

/**
* Get base sequence number.
*/
getBaseSequenceNumber(): number
{
return this.view.getUint16(12);
}

/**
* Get packet status count.
*/
getPacketStatusCount(): number
{
return this.view.getUint16(14);
}
}

0 comments on commit 967903f

Please sign in to comment.