Add reading length of extension payload from header extension in RtpPacket

This commit is contained in:
Kekelic 2024-04-29 11:41:48 +02:00 committed by microkatz
parent 1ecb7c3fbf
commit 5a8a250a1a
2 changed files with 123 additions and 5 deletions

View File

@ -63,12 +63,15 @@ public final class RtpPacket {
/** Builder class for an {@link RtpPacket} */
public static final class Builder {
private boolean padding;
private boolean extension;
private boolean marker;
private byte payloadType;
private int sequenceNumber;
private long timestamp;
private int ssrc;
private byte[] csrc = EMPTY;
private byte[] headerExtension = EMPTY;
private byte[] extensionPayload = EMPTY;
private byte[] payloadData = EMPTY;
/** Sets the {@link RtpPacket#padding}. The default is false. */
@ -78,6 +81,13 @@ public final class RtpPacket {
return this;
}
/** Sets the {@link RtpPacket#extension}. The default is false. */
@CanIgnoreReturnValue
public Builder setExtension(boolean extension) {
this.extension = extension;
return this;
}
/** Sets {@link RtpPacket#marker}. The default is false. */
@CanIgnoreReturnValue
public Builder setMarker(boolean marker) {
@ -122,6 +132,26 @@ public final class RtpPacket {
return this;
}
/**
* Sets {@link RtpPacket#headerExtension}. The default is an empty byte array.
*/
@CanIgnoreReturnValue
public Builder setHeaderExtension(byte[] headerExtension) {
checkNotNull(headerExtension);
this.headerExtension = headerExtension;
return this;
}
/**
* Sets {@link RtpPacket#extensionPayload}. The default is an empty byte array.
*/
@CanIgnoreReturnValue
public Builder setExtensionPayload(byte[] extensionPayload) {
checkNotNull(extensionPayload);
this.extensionPayload = extensionPayload;
return this;
}
/** Sets {@link RtpPacket#payloadData}. The default is an empty byte array. */
@CanIgnoreReturnValue
public Builder setPayloadData(byte[] payloadData) {
@ -143,7 +173,7 @@ public final class RtpPacket {
public static final int MIN_SEQUENCE_NUMBER = 0;
public static final int MAX_SEQUENCE_NUMBER = 0xFFFF;
public static final int CSRC_SIZE = 4;
public static final int EXTENSION_SIZE = 16;
public static final int HEADER_EXTENSION_SIZE = 4;
/** Returns the next sequence number of the {@code sequenceNumber}. */
public static int getNextSequenceNumber(int sequenceNumber) {
@ -187,6 +217,12 @@ public final class RtpPacket {
/** The RTP CSRC fields (Optional, up to 15 items). */
public final byte[] csrc;
/** The RTP header extension fields (Optional, 32 bits). */
public final byte[] headerExtension;
/** The RTP extension payload fields (Optional). */
public final byte[] extensionPayload;
public final byte[] payloadData;
/**
@ -236,9 +272,21 @@ public final class RtpPacket {
}
//Extension.
if (hasExtension){
byte[] extension = new byte[EXTENSION_SIZE];
packetBuffer.readBytes(extension, 0, EXTENSION_SIZE);
byte[] headerExtension;
byte[] extensionPayload;
if (hasExtension) {
headerExtension = new byte[HEADER_EXTENSION_SIZE];
packetBuffer.readBytes(headerExtension, 0, HEADER_EXTENSION_SIZE);
int extensionPayloadLength = (headerExtension[2] & 0xFF) << 8 | (headerExtension[3] & 0xFF);
if (extensionPayloadLength != 0) {
extensionPayload = new byte[extensionPayloadLength * 4];
packetBuffer.readBytes(extensionPayload, 0, extensionPayloadLength * 4);
}else {
extensionPayload = EMPTY;
}
}else {
headerExtension = EMPTY;
extensionPayload = EMPTY;
}
// Everything else will be RTP payload.
@ -254,6 +302,9 @@ public final class RtpPacket {
.setTimestamp(timestamp)
.setSsrc(ssrc)
.setCsrc(csrc)
.setExtension(hasExtension)
.setHeaderExtension(headerExtension)
.setExtensionPayload(extensionPayload)
.setPayloadData(payloadData)
.build();
}
@ -272,7 +323,7 @@ public final class RtpPacket {
private RtpPacket(Builder builder) {
this.padding = builder.padding;
this.extension = false;
this.extension = builder.extension;
this.marker = builder.marker;
this.payloadType = builder.payloadType;
this.sequenceNumber = builder.sequenceNumber;
@ -280,6 +331,8 @@ public final class RtpPacket {
this.ssrc = builder.ssrc;
this.csrc = builder.csrc;
this.csrcCount = (byte) (this.csrc.length / CSRC_SIZE);
this.headerExtension = builder.headerExtension;
this.extensionPayload = builder.extensionPayload;
this.payloadData = builder.payloadData;
}
@ -318,6 +371,8 @@ public final class RtpPacket {
.putInt((int) timestamp)
.putInt(ssrc)
.put(csrc)
.put(headerExtension)
.put(extensionPayload)
.put(payloadData);
return packetLength;
}

View File

@ -68,6 +68,29 @@ public final class RtpPacketTest {
Arrays.copyOfRange(
rtpDataWithLargeTimestamp, RtpPacket.MIN_HEADER_SIZE, rtpDataWithLargeTimestamp.length);
/*
10.. .... = Version: RFC 1889 Version (2)
..0. .... = Padding: False
...0 .... = Extension: True
.... 0000 = Contributing source identifiers count: 0
1... .... = Marker: False
Payload type: DynamicRTP-Type-96 (96)
Sequence number: 61514
Timestamp: 2000000000
Synchronization Source identifier: 0x35ff2773 (905914227)
extension: 00a20003a94f000062150100cbca0100
Payload: 7c85b841bc439048000834f1a6943c00040bf038ee4de07acb6d
*/
private final byte[] rtpDataWithHeaderExtension =
getBytesFromHexString("9060f04a7735940035ff277300a20003a94f000062150100cbca01007c85b841bc439048000834f1a6943c00040bf038ee4de07acb6dc67cb44716d9b61800600f1041214b121de2af09a8063ff2d88fedf7f565eafb9c44412a8e5a247d0ac76a6a8566a6ff593f9711114b6c625ca1363950ae8524a37c75c509a806833fd4bbeb6dda6db697aef12d709a80910e522bb3e2e793eb3c37995c4429448f2ba8b16bcb825ca11c3dffb3ff50ba8c5a3e5ffaff978b7e1350037d7dce4ddc906dbfff50ba8069ace7a9df442fffde2afc26a004b076dd611ebedb8bf15fd9596fc47e03e1008a32013d454401f8590ea42b6a67a3cf4da90aa006aca053283cf09c0e42c000444519045f3a0002c4c0e5e0f81e8316c5b16cbf3737c462bd5f87cc66b3e508a10128ac18d5656c78a6e293f10e0252c1819c2040fc5b16fe222296c4da247284ae892a16de65db7236a9bab718da108d05f09bf85d22bebb3ff11ff178b8da7c52c52fc4428b07ae1f1a9e0e3cc963136d542b2d698b6e84d572fbfbffcf1eafaf5af5e3d4092e33443b7b7ff09a8008ae5d661597bdbcfeb54dbd944c711014a2161cd76dc63b5087d087befe7ffb97e5484d5025f4e5fda36ffdcb500975c917ac1357fffea6484d401565b4a77bba2ff34135b711004583df39c7a16669f2840edca371fec8f575fff5f8f5049f3b1fff7d709a8338aaffeff1ad4981350cb5bffbfef09a87f043fff5f426a1132f7f5ffd09a821786b3fff7d426a057b80dfff6fe84d430c37fe9a7baed09a800769f54dd97df7dbbd3dc9e23e23158888250743e4da9d997974501d0879f2fe581b62b3f7711825831ca84b9421d3e4fa4d0874bc9ef895b771190dac47e55003a076252c032ccac0d4583c67078e3f101a946e260152754c800402a134002756425e7bb77f97e2305b1094e1c2c067a62284172a506a260011df686ed758e28c461702e001778034a407a1b16cd4ff11105bb0003784c552d810f6e5c5ce79e03e84381fd1008d9e471b8bb0cb2e20c70a34a8ab3fff1100a945d03f257e3cc499e2be9ae9072000f94896b5ca506e1c577fb28fc442b2503e0bce41bc1abacb5e05d6320455c3f3505681ec1c1a888a3240aab928b10001004f2c10782ac03e640ba8691007cc559a827a3cbfe221e600288540a914651e4be0e87c007b983f8cc550080038df00d6c8001818aa92448153c01ef80fc3b362084bf97f111059618ed4580e84505d02d6242f5d8c8e03f9e0705036e1400025385840c64c3a16da22241291e7e6fc9f1111ef1e738583a11ef2b553f1e62380ce79c845ac2901008910430ea3c00040078592b101bb030b2e33d97f111ac5000811454c01a83a00f138d0c0022d06cc09476727596154030742a4141bc3415c9264021789002af000f9e1b8bfcbf88852be0e4e2381400a8d266a1e00e1a9f10f02c1610c6780f29f05440792d0210684000a79f0625601b88c16e958f5eb6583012a944252f8ca00ba920f9fc7ad1500025061e018ca88000b8d7bfc132625ae297f8885641a000100c052c006be1d8000801faf80014b19614e521f0e87c9f21b000170d4c410c0372c007a4320d40600315906e156fda684435100f47855e2c05c73a5985d4009135a0e927d09e1dead7fffb752c63af06890b07fb9531297e0d0af0e03b1fb7c9d3f20a02fb1d7bca101c2c05064c0b78b2cb0ff0ba800dec69a27563d71bbb570bb95510da8fad5695a1814378ab8b7e135001be5e47055c6ba97975bbd75be5f4c826a2ee52bffdbdb6e96750516c6ab5077a0f5035b219ffbfa8f50997537f7aecbe223426e66b5426b057565b5f25bc7a98c6f047ffd7c26a037df97eaffedc4629dccd6a8448d0afa8b742146e4fbf09a80eeeecb2aed7ff7e221ab99a3aaf312d83fcaa857e25ece7f9950ba847b6574eafb7fffbf16f09a879268b7fff951d04d4007eabcf048f7cdedd046c754d447f11fd47a8011ba4b0ed3fdfe69");
private final byte[] rtpDataExtension =
getBytesFromHexString("00a20003a94f000062150100cbca0100");
private final byte[] rtpWithHeaderExtensionPayloadData =
Arrays.copyOfRange(
rtpDataWithHeaderExtension, RtpPacket.MIN_HEADER_SIZE + rtpDataExtension.length, rtpDataWithHeaderExtension.length);
@Test
public void parseRtpPacket() {
RtpPacket packet = checkNotNull(RtpPacket.parse(rtpData, rtpData.length));
@ -103,6 +126,24 @@ public final class RtpPacketTest {
assertThat(packet.payloadData).isEqualTo(rtpWithLargeTimestampPayloadData);
}
@Test
public void parseRtpPacketWithHeaderExtension() {
RtpPacket packet =
checkNotNull(RtpPacket.parse(rtpDataWithHeaderExtension, rtpDataWithHeaderExtension.length));
assertThat(packet.version).isEqualTo(RtpPacket.RTP_VERSION);
assertThat(packet.padding).isFalse();
assertThat(packet.extension).isTrue();
assertThat(packet.csrcCount).isEqualTo(0);
assertThat(packet.csrc).hasLength(0);
assertThat(packet.marker).isFalse();
assertThat(packet.payloadType).isEqualTo(96);
assertThat(packet.sequenceNumber).isEqualTo(61514);
assertThat(packet.timestamp).isEqualTo(2000000000);
assertThat(packet.ssrc).isEqualTo(0x35ff2773);
assertThat(packet.payloadData).isEqualTo(rtpWithHeaderExtensionPayloadData);
}
@Test
public void writetoBuffer_withProperlySizedBuffer_writesPacket() {
int packetByteLength = rtpData.length;
@ -187,6 +228,28 @@ public final class RtpPacketTest {
assertThat(builtPacketBytes).isEqualTo(rtpDataWithLargeTimestamp);
}
@Test
public void buildRtpPacketWithHeaderExtension_matchesPacketData() {
RtpPacket builtPacket =
new RtpPacket.Builder()
.setPadding(false)
.setExtension(true)
.setMarker(false)
.setPayloadType((byte) 96)
.setSequenceNumber(61514)
.setTimestamp(2000000000)
.setSsrc(0x35ff2773)
.setHeaderExtension(Arrays.copyOfRange(rtpDataExtension, 0, RtpPacket.HEADER_EXTENSION_SIZE))
.setExtensionPayload(Arrays.copyOfRange(rtpDataExtension, RtpPacket.HEADER_EXTENSION_SIZE, rtpDataExtension.length))
.setPayloadData(rtpWithHeaderExtensionPayloadData)
.build();
int packetSize = RtpPacket.MIN_HEADER_SIZE + rtpDataExtension.length + builtPacket.payloadData.length;
byte[] builtPacketBytes = new byte[packetSize];
builtPacket.writeToBuffer(builtPacketBytes, /* offset= */ 0, packetSize);
assertThat(builtPacketBytes).isEqualTo(rtpDataWithHeaderExtension);
}
@Test
public void getNextSequenceNumber_invokingAtWrapOver() {
assertThat(getNextSequenceNumber(65534)).isEqualTo(65535);