RtpPacket.java
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.exoplayer.rtsp;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.math.IntMath;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.nio.ByteBuffer;
/**
* Represents the header and the payload of an RTP packet.
*
* <p>Not supported parsing at the moment: header extension and CSRC.
*
* <p>Structure of an RTP header (RFC3550, Section 5.1).
*
* <pre>
* 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|X| CC |M| PT | sequence number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | synchronization source (SSRC) identifier |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | contributing source (CSRC) identifiers |
* | .... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | Profile-specific extension ID | Extension header length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Extension header |
* | .... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 3 2 1
* 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
* </pre>
*/
@UnstableApi
public final class RtpPacket {
/** Builder class for an {@link RtpPacket} */
public static final class Builder {
private boolean padding;
private boolean marker;
private byte payloadType;
private int sequenceNumber;
private long timestamp;
private int ssrc;
private byte[] csrc = EMPTY;
private byte[] payloadData = EMPTY;
/** Sets the {@link RtpPacket#padding}. The default is false. */
@CanIgnoreReturnValue
public Builder setPadding(boolean padding) {
this.padding = padding;
return this;
}
/** Sets {@link RtpPacket#marker}. The default is false. */
@CanIgnoreReturnValue
public Builder setMarker(boolean marker) {
this.marker = marker;
return this;
}
/** Sets {@link RtpPacket#payloadType}. The default is 0. */
@CanIgnoreReturnValue
public Builder setPayloadType(byte payloadType) {
this.payloadType = payloadType;
return this;
}
/** Sets {@link RtpPacket#sequenceNumber}. The default is 0. */
@CanIgnoreReturnValue
public Builder setSequenceNumber(int sequenceNumber) {
checkArgument(sequenceNumber >= MIN_SEQUENCE_NUMBER && sequenceNumber <= MAX_SEQUENCE_NUMBER);
this.sequenceNumber = sequenceNumber & 0xFFFF;
return this;
}
/** Sets {@link RtpPacket#timestamp}. The default is 0. */
@CanIgnoreReturnValue
public Builder setTimestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
/** Sets {@link RtpPacket#ssrc}. The default is 0. */
@CanIgnoreReturnValue
public Builder setSsrc(int ssrc) {
this.ssrc = ssrc;
return this;
}
/** Sets {@link RtpPacket#csrc}. The default is an empty byte array. */
@CanIgnoreReturnValue
public Builder setCsrc(byte[] csrc) {
checkNotNull(csrc);
this.csrc = csrc;
return this;
}
/** Sets {@link RtpPacket#payloadData}. The default is an empty byte array. */
@CanIgnoreReturnValue
public Builder setPayloadData(byte[] payloadData) {
checkNotNull(payloadData);
this.payloadData = payloadData;
return this;
}
/** Builds the {@link RtpPacket}. */
public RtpPacket build() {
return new RtpPacket(this);
}
}
public static final int RTP_VERSION = 2;
public static final int MAX_SIZE = 65507;
public static final int MIN_HEADER_SIZE = 12;
public static final int MIN_SEQUENCE_NUMBER = 0;
public static final int MAX_SEQUENCE_NUMBER = 0xFFFF;
public static final int CSRC_SIZE = 4;
/** Returns the next sequence number of the {@code sequenceNumber}. */
public static int getNextSequenceNumber(int sequenceNumber) {
return IntMath.mod(sequenceNumber + 1, MAX_SEQUENCE_NUMBER + 1);
}
/** Returns the previous sequence number from the {@code sequenceNumber}. */
public static int getPreviousSequenceNumber(int sequenceNumber) {
return IntMath.mod(sequenceNumber - 1, MAX_SEQUENCE_NUMBER + 1);
}
private static final byte[] EMPTY = new byte[0];
/** The RTP version field (Word 0, bits 0-1), should always be 2. */
public final byte version = RTP_VERSION;
/** The RTP padding bit (Word 0, bit 2). */
public final boolean padding;
/** The RTP extension bit (Word 0, bit 3). */
public final boolean extension;
/** The RTP CSRC count field (Word 0, bits 4-7). */
public final byte csrcCount;
/** The RTP marker bit (Word 0, bit 8). */
public final boolean marker;
/** The RTP CSRC count field (Word 0, bits 9-15). */
public final byte payloadType;
/** The RTP sequence number field (Word 0, bits 16-31). */
public final int sequenceNumber;
/** The RTP timestamp field (Word 1). */
public final long timestamp;
/** The RTP SSRC field (Word 2). */
public final int ssrc;
/** The RTP CSRC fields (Optional, up to 15 items). */
public final byte[] csrc;
public final byte[] payloadData;
/**
* Creates an {@link RtpPacket} from a {@link ParsableByteArray}.
*
* @param packetBuffer The buffer that contains the RTP packet data.
* @return The built {@link RtpPacket}.
*/
@Nullable
public static RtpPacket parse(ParsableByteArray packetBuffer) {
if (packetBuffer.bytesLeft() < MIN_HEADER_SIZE) {
return null;
}
// Word 0.
int firstByte = packetBuffer.readUnsignedByte();
byte version = (byte) (firstByte >> 6);
boolean padding = ((firstByte >> 5) & 0x1) == 1;
byte csrcCount = (byte) (firstByte & 0xF);
if (version != RTP_VERSION) {
return null;
}
int secondByte = packetBuffer.readUnsignedByte();
boolean marker = ((secondByte >> 7) & 0x1) == 1;
byte payloadType = (byte) (secondByte & 0x7F);
int sequenceNumber = packetBuffer.readUnsignedShort();
// Word 1.
long timestamp = packetBuffer.readUnsignedInt();
// Word 2.
int ssrc = packetBuffer.readInt();
// CSRC.
byte[] csrc;
if (csrcCount > 0) {
csrc = new byte[csrcCount * CSRC_SIZE];
for (int i = 0; i < csrcCount; i++) {
packetBuffer.readBytes(csrc, i * CSRC_SIZE, CSRC_SIZE);
}
} else {
csrc = EMPTY;
}
// Everything else will be RTP payload.
byte[] payloadData = new byte[packetBuffer.bytesLeft()];
packetBuffer.readBytes(payloadData, 0, packetBuffer.bytesLeft());
Builder builder = new Builder();
return builder
.setPadding(padding)
.setMarker(marker)
.setPayloadType(payloadType)
.setSequenceNumber(sequenceNumber)
.setTimestamp(timestamp)
.setSsrc(ssrc)
.setCsrc(csrc)
.setPayloadData(payloadData)
.build();
}
/**
* Creates an {@link RtpPacket} from a byte array.
*
* @param buffer The buffer that contains the RTP packet data.
* @param length The length of the RTP packet.
* @return The built {@link RtpPacket}.
*/
@Nullable
public static RtpPacket parse(byte[] buffer, int length) {
return parse(new ParsableByteArray(buffer, length));
}
private RtpPacket(Builder builder) {
this.padding = builder.padding;
this.extension = false;
this.marker = builder.marker;
this.payloadType = builder.payloadType;
this.sequenceNumber = builder.sequenceNumber;
this.timestamp = builder.timestamp;
this.ssrc = builder.ssrc;
this.csrc = builder.csrc;
this.csrcCount = (byte) (this.csrc.length / CSRC_SIZE);
this.payloadData = builder.payloadData;
}
/**
* Writes the data in an RTP packet to a target buffer.
*
* <p>The size of the target buffer and the length argument should be big enough so that the
* entire RTP packet could fit. That is, if there is not enough space to store the entire RTP
* packet, no bytes will be written. The maximum size of an RTP packet is defined as {@link
* RtpPacket#MAX_SIZE}.
*
* @param target A target byte buffer to which the packet data is copied.
* @param offset The offset into the target array at which to write.
* @param length The maximum number of bytes that can be written.
* @return The number of bytes written, or {@link C#LENGTH_UNSET} if there is not enough space to
* write the packet.
*/
public int writeToBuffer(byte[] target, int offset, int length) {
int packetLength = MIN_HEADER_SIZE + (CSRC_SIZE * csrcCount) + payloadData.length;
if (length < packetLength || target.length - offset < packetLength) {
return C.LENGTH_UNSET;
}
ByteBuffer buffer = ByteBuffer.wrap(target, offset, length);
byte firstByte =
(byte)
((version << 6)
| ((padding ? 1 : 0) << 5)
| ((extension ? 1 : 0) << 4)
| (csrcCount & 0xF));
byte secondByte = (byte) (((marker ? 1 : 0) << 7) | (payloadType & 0x7F));
buffer
.put(firstByte)
.put(secondByte)
.putShort((short) sequenceNumber)
.putInt((int) timestamp)
.putInt(ssrc)
.put(csrc)
.put(payloadData);
return packetLength;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RtpPacket rtpPacket = (RtpPacket) o;
return payloadType == rtpPacket.payloadType
&& sequenceNumber == rtpPacket.sequenceNumber
&& marker == rtpPacket.marker
&& timestamp == rtpPacket.timestamp
&& ssrc == rtpPacket.ssrc;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + payloadType;
result = 31 * result + sequenceNumber;
result = 31 * result + (marker ? 1 : 0);
result = 31 * result + (int) (timestamp ^ (timestamp >>> 32));
result = 31 * result + ssrc;
return result;
}
@Override
public String toString() {
return Util.formatInvariant(
"RtpPacket(payloadType=%d, seq=%d, timestamp=%d, ssrc=%x, marker=%b)",
payloadType, sequenceNumber, timestamp, ssrc, marker);
}
}