-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |