Skip to content

Commit

Permalink
Add RTCP SLI feedback packet
Browse files Browse the repository at this point in the history
  • Loading branch information
ibc committed Aug 17, 2023
1 parent 5cb48b0 commit 392d831
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 0 deletions.
221 changes: 221 additions & 0 deletions src/packets/RTCP/SliPacket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { RtcpPacketType } from './RtcpPacket';
import {
FeedbackPacket,
PsFeedbackMessageType,
FeedbackPacketDump,
FIXED_HEADER_LENGTH
} from './FeedbackPacket';

/**
* RTCP SLI packet info dump.
*
* @category RTCP
*/
export type SliPacketDump = FeedbackPacketDump &
{
items: { first: number; number: number; pictureId: number }[];
};

/**
* RTCP SLI packet (RTCP Payload Specific 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=2 | PT=PSFB=206 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of packet sender |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of media source |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | First | Number | PictureID |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* ```
*
* @category RTCP
*
* @see
* - [RFC 4585 section 6.3.2](https://datatracker.ietf.org/doc/html/rfc4585#section-6.3.2)
*/
export class SliPacket extends FeedbackPacket
{
#items: { first: number; number: number; pictureId: number }[] = [];

/**
* @param view - If given it will be parsed. Otherwise an empty RTCP SLI
* packet will be created.
*
* @throws
* - If given `view` does not contain a valid RTCP SLI packet.
*/
constructor(view?: DataView)
{
super(RtcpPacketType.PSFB, PsFeedbackMessageType.SLI, 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 first = this.view.getUint16(pos) >> 3;

const number =
(this.view.getUint32(pos) & 0b00000000000001111111111111000000) >> 6;

const pictureId = this.view.getUint8(pos + 3) & 0b00111111;

pos += 4;

this.#items.push({ first, number, pictureId });
}

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 SLI packet info.
*/
dump(): SliPacketDump
{
return {
...super.dump(),
items : this.getItems()
};
}

/**
* @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 { first, number, pictureId } of this.#items)
{
view.setUint32(
pos,
(first << 19) + (number << 6) + pictureId
);

pos += 4;
}

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
): SliPacket
{
const view = this.cloneInternal(
buffer,
byteOffset,
serializationBuffer,
serializationByteOffset
);

return new SliPacket(view);
}

/**
* Get SLI items.
*/
getItems(): { first: number; number: number; pictureId: number }[]
{
return Array.from(this.#items);
}

/**
* Set SLI items.
*
* @remarks
* - Serialization is needed after calling this method.
*/
setItems(items: { first: number; number: number; pictureId: number }[]): void
{
this.#items = Array.from(items);

this.setSerializationNeeded(true);
}

/**
* Add SLI item value.
*
* @remarks
* - Serialization is needed after calling this method.
*/
addItem(first: number, number: number, pictureId: number): void
{
this.#items.push({ first, number, pictureId });

this.setSerializationNeeded(true);
}
}
5 changes: 5 additions & 0 deletions src/packets/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ export {
PliPacketDump
} from './RTCP/PliPacket';

export {
SliPacket,
SliPacketDump
} from './RTCP/SliPacket';

export {
GenericFeedbackPacket,
GenericFeedbackPacketDump
Expand Down
87 changes: 87 additions & 0 deletions src/tests/packets/RTCP/SliPacket.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
SliPacket,
SliPacketDump
} from '../../../packets/RTCP/SliPacket';
import { PsFeedbackMessageType } from '../../../packets/RTCP/FeedbackPacket';
import { isRtcp, RtcpPacketType } from '../../../packets/RTCP/RtcpPacket';
import { areDataViewsEqual } from '../../../utils/helpers';

const sliPacketDump: SliPacketDump =
{
byteLength : 20,
padding : 0,
packetType : RtcpPacketType.PSFB,
count : 2, // Used to indicate FMT, so 2 for SLI.
messageType : PsFeedbackMessageType.SLI,
senderSsrc : 0x11223344,
mediaSsrc : 0x55667788,
items :
[
{ first: 0b1100000000011, number: 0b1111000001111, pictureId: 0b101010 },
{ first: 0b0100000000011, number: 0b0111000001111, pictureId: 0b001010 }
]
};

describe('parse RTCP SLI packet', () =>
{
const array = new Uint8Array(
[
0x82, 0xce, 0x00, 0x04, // FMT: 2 (SLI), Type: 206 (PSFB), Count: 0, length: 4
0x11, 0x22, 0x33, 0x44, // Sender SSRC: 0x11223344
0x55, 0x66, 0x77, 0x88, // Media SSRC: 0x55667788
0b11000000, 0b00011111, 0b10000011, 0b11101010, // Item
0b01000000, 0b00011011, 0b10000011, 0b11001010 // Item
]
);

const view = new DataView(
array.buffer,
array.byteOffset,
array.byteLength
);

test('buffer view is RTCP', () =>
{
expect(isRtcp(view)).toBe(true);
});

test('packet processing succeeds', () =>
{
const packet = new SliPacket(view);

expect(packet.needsSerialization()).toBe(false);
expect(packet.dump()).toEqual(sliPacketDump);
expect(packet.needsSerialization()).toBe(false);
expect(areDataViewsEqual(packet.getView(), view)).toBe(true);

packet.serialize();
expect(packet.dump()).toEqual(sliPacketDump);
expect(areDataViewsEqual(packet.getView(), view)).toBe(true);

const clonedPacket = packet.clone();

expect(clonedPacket.dump()).toEqual(sliPacketDump);
expect(areDataViewsEqual(clonedPacket.getView(), view)).toBe(true);
});
});

describe('create RTCP SLI packet', () =>
{
test('creating a SLI packet with padding succeeds', () =>
{
const packet = new SliPacket();

packet.setSenderSsrc(sliPacketDump.senderSsrc);
packet.setMediaSsrc(sliPacketDump.mediaSsrc);
packet.setItems(sliPacketDump.items);

expect(packet.dump()).toEqual(sliPacketDump);

packet.serialize();
expect(packet.dump()).toEqual(sliPacketDump);

const clonedPacket = packet.clone();

expect(clonedPacket.dump()).toEqual(sliPacketDump);
});
});

0 comments on commit 392d831

Please sign in to comment.