UNPKG

rtp.js

Version:

RTP stack for Node.js and browser written in TypeScript

190 lines (189 loc) 7.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RpsiPacket = void 0; const RtcpPacket_1 = require("./RtcpPacket"); const FeedbackPacket_1 = require("./FeedbackPacket"); const helpers_1 = require("../../utils/helpers"); const bitOps_1 = require("../../utils/bitOps"); const MAX_BIT_STRING_LENGTH = 6; /** * RTCP RPSI 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=3 | PT=PSFB=206 | length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SSRC of packet sender | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SSRC of media source | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | PB |0| Payload Type| Native RPSI bit string | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | defined per codec ... | Padding (0) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * ``` * * @category RTCP * * @see * - [RFC 4585 section 6.3.3](https://datatracker.ietf.org/doc/html/rfc4585#section-6.3.3) */ class RpsiPacket extends FeedbackPacket_1.FeedbackPacket { // Buffer view holding the bit string. #bitStringView; /** * @param view - If given it will be parsed. Otherwise an empty RTCP RPSI * packet will be created. * * @throws * - If given `view` does not contain a valid RTCP RPSI packet. */ constructor(view) { super(RtcpPacket_1.RtcpPacketType.PSFB, FeedbackPacket_1.PsFeedbackMessageType.RPSI, view); if (!this.view) { this.view = new DataView(new ArrayBuffer(FeedbackPacket_1.FIXED_HEADER_LENGTH + 4)); // Write version, packet type and feedback message type. this.writeFixedHeader(); // Set string with length 2 bytes. this.#bitStringView = new DataView(this.view.buffer, this.view.byteOffset + FeedbackPacket_1.FIXED_HEADER_LENGTH + 2, 2); return; } // Position relative to the DataView byte offset. let pos = 0; // Move to padding bits. pos += FeedbackPacket_1.FIXED_HEADER_LENGTH; const fciPaddingBits = this.view.getUint8(pos); if (fciPaddingBits % 8 !== 0) { throw new TypeError('invalid RPSI packet with fractional number of padding bytes'); } const fciPaddingBytes = fciPaddingBits / 8; if (fciPaddingBytes > MAX_BIT_STRING_LENGTH) { throw new TypeError('too many padding bytes'); } // Move to bit string. pos += 2; // Get bit string. const bitStringLength = this.view.byteLength - pos - fciPaddingBytes - this.padding; this.#bitStringView = new DataView(this.view.buffer, this.view.byteOffset + pos, bitStringLength); pos += bitStringLength + fciPaddingBytes + 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 RPSI packet info. */ dump() { return { ...super.dump(), payloadType: this.getPayloadType(), bitStringLength: this.getBitString().byteLength, paddingBits: this.getFciPaddingBits(), }; } /** * @inheritDoc */ getByteLength() { if (!this.needsSerialization()) { return this.view.byteLength; } const packetLength = FeedbackPacket_1.FIXED_HEADER_LENGTH + 2 + this.#bitStringView.byteLength + this.getFciPaddingBits() / 8 + this.padding; return packetLength; } /** * @inheritDoc */ serialize(buffer, byteOffset) { const view = this.serializeBase(buffer, byteOffset); const uint8Array = new Uint8Array(view.buffer, view.byteOffset, view.byteLength); // Position relative to the DataView byte offset. let pos = 0; // Move to padding bits. pos += FeedbackPacket_1.FIXED_HEADER_LENGTH; // Copy the rest of the fixed fields (padding bits and payload type) into // the new buffer. uint8Array.set(new Uint8Array(this.view.buffer, this.view.byteOffset + pos, 2), pos); // Move to padding bit string. pos += 2; // Copy the bit string into the new buffer. uint8Array.set(new Uint8Array(this.#bitStringView.buffer, this.#bitStringView.byteOffset, this.#bitStringView.byteLength), pos); // Create new bit string DataView. const bitStringView = new DataView(view.buffer, view.byteOffset + pos, this.#bitStringView.byteLength); // Move to FCI padding. pos += bitStringView.byteLength; const fciPaddingBytes = this.getFciPaddingBits() / 8; // Fill padding bytes with zeros. for (let i = 0; i < fciPaddingBytes; ++i) { view.setUint8(pos + i, 0); } // Move to packet padding. pos += fciPaddingBytes; 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, byteOffset, serializationBuffer, serializationByteOffset) { const view = this.cloneInternal(buffer, byteOffset, serializationBuffer, serializationByteOffset); return new RpsiPacket(view); } /** * Get payload type. */ getPayloadType() { return (0, bitOps_1.readBitsInDataView)({ view: this.view, pos: 13, mask: 0b01111111 }); } /** * Set the payload type. */ setPayloadType(payloadType) { (0, bitOps_1.writeBitsInDataView)({ view: this.view, pos: 13, mask: 0b01111111, value: payloadType, }); } /** * Get the bit string. */ getBitString() { return this.#bitStringView; } /** * Set the bit string. * * @remarks * - Serialization is needed after calling this method. */ setBitString(view) { this.#bitStringView = view; const fciLength = 2 + this.#bitStringView.byteLength; const fciLaddedLength = (0, helpers_1.padTo4Bytes)(fciLength); const paddingBytes = fciLaddedLength - fciLength; this.setFciPaddingBits(paddingBytes * 8); this.setSerializationNeeded(true); } getFciPaddingBits() { return this.view.getUint8(12); } setFciPaddingBits(paddingBits) { return this.view.setUint8(12, paddingBits); } } exports.RpsiPacket = RpsiPacket;