Skip to content

Commit

Permalink
Add createNackItem() and parseNackItem()
Browse files Browse the repository at this point in the history
  • Loading branch information
ibc committed Aug 17, 2023
1 parent 5cfbbd3 commit 5c0575e
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 1 deletion.
54 changes: 54 additions & 0 deletions src/packets/RTCP/NackPacket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
FeedbackPacketDump,
FIXED_HEADER_LENGTH
} from './FeedbackPacket';
import { readBit, writeBit } from '../../utils/bitOps';

/**
* RTCP NACK packet info dump.
Expand Down Expand Up @@ -219,3 +220,56 @@ export class NackPacket extends FeedbackPacket
this.setSerializationNeeded(true);
}
}

/**
* Parse a NACK item. It returns an array with RTP sequence numbers that are
* included in the item (lost packets).
*
* @category RTCP
*/
export function parseNackItem(pid: number, bitmask: number): number[]
{
const lostSeqs: number[] = [ pid ];

for (let i = 0; i <= 15; ++i)
{
if (readBit({ value: bitmask, bit: i }))
{
lostSeqs.push(pid + i + 1);
}
}

return lostSeqs;
}

/**
* Create a NACK item.
*
* @param seqs - RTP sequence number of lost packets. As per NACK rules, there
* can be up to 17 seq numbers and max diff between any two of them must be 17.
*
* @category RTCP
*/
export function createNackItem(
lostSeqs: number[]
): { pid: number; bitmask: number }
{
const orderedLostSeqs = [ ...lostSeqs ].sort();
const pid = orderedLostSeqs[0];
let bitmask: number = 0;

for (let i = 1; i < orderedLostSeqs.length; ++i)
{
const seq = orderedLostSeqs[i];
const diff = (seq + 65536 - pid) % 65536;

if (diff > 16)
{
throw new RangeError('cannot create a NACK bitmask with given seq numbers');
}

bitmask = writeBit({ value: bitmask, bit: diff - 1, flag: true });
}

return { pid, bitmask };
}
4 changes: 3 additions & 1 deletion src/packets/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ export {

export {
NackPacket,
NackPacketDump
NackPacketDump,
parseNackItem,
createNackItem
} from './RTCP/NackPacket';

export {
Expand Down
117 changes: 117 additions & 0 deletions src/tests/packets/RTCP/NackPacket.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
NackPacket,
NackPacketDump,
parseNackItem,
createNackItem
} from '../../../packets/RTCP/NackPacket';
import { RtpFeedbackMessageType } from '../../../packets/RTCP/FeedbackPacket';
import { isRtcp, RtcpPacketType } from '../../../packets/RTCP/RtcpPacket';
import { areDataViewsEqual } from '../../../utils/helpers';

const nackPacketDump: NackPacketDump =
{
byteLength : 20,
padding : 0,
packetType : RtcpPacketType.RTPFB,
count : 1, // Used to indicate FMT, so 1 for NACK.
messageType : RtpFeedbackMessageType.NACK,
senderSsrc : 0x11223344,
mediaSsrc : 0x55667788,
items :
[
{ pid: 100, bitmask: 0b1010101010101010 },
{ pid: 10000, bitmask: 0b0000111100001111 }
]
};

describe('parse RTCP NACK packet', () =>
{
const array = new Uint8Array(
[
0x81, 0xcd, 0x00, 0x04, // FMT: 1 (NACK), Type: 205 (RTPFB), Count: 0, length: 4
0x11, 0x22, 0x33, 0x44, // Sender SSRC: 0x11223344
0x55, 0x66, 0x77, 0x88, // Media SSRC: 0x55667788
0x00, 0x64, 0b10101010, 0b10101010, // PID: 100, bitmask: 0b1010101010101010
0x27, 0x10, 0b00001111, 0b00001111 // PID: 10000, bitmask: 0b0000111100001111
]
);

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 NackPacket(view);

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

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

const clonedPacket = packet.clone();

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

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

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

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

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

const clonedPacket = packet.clone();

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

describe('NACK items', () =>
{
const nackItem1 = nackPacketDump.items[0];
const nackItem2 = nackPacketDump.items[1];

test('parseNackItem()', () =>
{
expect(parseNackItem(nackItem1.pid, nackItem1.bitmask)).toEqual(
[ 100, 102, 104, 106, 108, 110, 112, 114, 116 ]
);

expect(parseNackItem(nackItem2.pid, nackItem2.bitmask)).toEqual(
[ 10000, 10001, 10002, 10003, 10004, 10009, 10010, 10011, 10012 ]
);
});

test('createNackItem()', () =>
{
expect(createNackItem(
[ 100, 102, 104, 106, 108, 110, 112, 114, 116 ]
)).toEqual(nackItem1);

expect(createNackItem(
[ 10000, 10001, 10002, 10003, 10004, 10009, 10010, 10011, 10012 ]
)).toEqual(nackItem2);
});
});

0 comments on commit 5c0575e

Please sign in to comment.