sas.systems.imflux.packet.DataPacket.java Source code

Java tutorial

Introduction

Here is the source code for sas.systems.imflux.packet.DataPacket.java

Source

/*
 * Copyright 2015 Sebastian Schmidl
 *
 * 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 sas.systems.imflux.packet;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import java.util.ArrayList;
import java.util.List;

/**
 * This class serves as the container for the to be transferred data.
 * 
 * RTP fixed header fields:
 * <pre>
 *  0               1               2               3                bytes
 *  0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7  bits
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |V=2|P|X|  CC   |M|     PT      |       sequence number         | 
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                           timestamp                           |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |           synchronization source (SSRC) identifier            |
 * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
 * |            contributing source (CSRC) identifiers             |
 * |                             ....                              |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * </pre>
 * Optional header extension (payload-format-independent)
 * <pre>
 *  0               1               2               3                bytes
 *  0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7  bits
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |      defined by profile       |           length              |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                        header extension                       |
 * |                             ....                              |
 * </pre>
 * @author <a href="http://bruno.biasedbit.com/">Bruno de Carvalho</a>
 * @author <a href="https://github.com/CodeLionX">CodeLionX</a>
 */
public class DataPacket {

    // internal vars --------------------------------------------------------------------------------------------------
    private RtpVersion version; // only version 2 is supported!
    private boolean marker; // profile dependent
    private int payloadType;
    private int sequenceNumber;
    private long timestamp;
    private long ssrc;

    private short extensionHeaderData;
    private byte[] extensionData;

    private List<Long> contributingSourceIds;

    private ByteBuf data;

    // constructors ---------------------------------------------------------------------------------------------------
    public DataPacket() {
        this.version = RtpVersion.V2;
    }

    // public static methods ------------------------------------------------------------------------------------------
    /**
     * Decodes a {@code DataPacket}.
     * 
     * @param data as a byte-array
     * @return the DataPacket object
     */
    public static DataPacket decode(byte[] data) {
        return decode(Unpooled.wrappedBuffer(data));
    }

    /**
     * Decodes a {@code DataPacket}.
     * 
     * @param buffer as a ByteBuf
     * @return the DataPacket object
     * @throws IndexOutOfBoundsException
     */
    public static DataPacket decode(ByteBuf buffer) throws IndexOutOfBoundsException {
        if (buffer.readableBytes() < 12) {
            throw new IllegalArgumentException("A RTP packet must be at least 12 octets long");
        }

        // Version, Padding, eXtension, CSRC Count
        DataPacket packet = new DataPacket();
        byte b = buffer.readByte();
        packet.version = RtpVersion.fromByte(b);
        boolean padding = (b & 0x20) > 0; // mask 0010 0000
        boolean extension = (b & 0x10) > 0; // mask 0001 0000
        int contributingSourcesCount = b & 0x0f; // mask 0000 1111

        // Marker, Payload Type
        b = buffer.readByte();
        packet.marker = (b & 0x80) > 0; // mask 1000 0000
        packet.payloadType = (b & 0x7f); // mask 0111 1111

        packet.sequenceNumber = buffer.readUnsignedShort();
        packet.timestamp = buffer.readUnsignedInt();
        packet.ssrc = buffer.readUnsignedInt();

        // Read extension headers & data
        if (extension) {
            packet.extensionHeaderData = buffer.readShort();
            packet.extensionData = new byte[buffer.readUnsignedShort() * 4];
            buffer.readBytes(packet.extensionData);
        }

        // Read CCRC's
        if (contributingSourcesCount > 0) {
            packet.contributingSourceIds = new ArrayList<Long>(contributingSourcesCount);
            for (int i = 0; i < contributingSourcesCount; i++) {
                long contributingSource = buffer.readUnsignedInt();
                packet.contributingSourceIds.add(contributingSource);
            }
        }

        if (!padding) {
            // No padding used, assume remaining data is the packet
            byte[] remainingBytes = new byte[buffer.readableBytes()];
            buffer.readBytes(remainingBytes);
            packet.setData(remainingBytes);
        } else {
            // Padding bit was set, so last byte contains the number of padding octets that should be discarded.
            short lastByte = buffer.getUnsignedByte(buffer.readerIndex() + buffer.readableBytes() - 1);
            byte[] dataBytes = new byte[buffer.readableBytes() - lastByte];
            buffer.readBytes(dataBytes);
            packet.setData(dataBytes);
            // Discard rest of buffer.
            buffer.skipBytes(buffer.readableBytes());
        }

        return packet;
    }

    /**
     * Encodes the specified DataPacket.
     * 
     * @param fixedBlockSize set this param, if the packet should have a fixed block size (have a padding)
     * @param packet the DataPacket to be encoded
     * @return a {@link ByteBuf} containing the bytes
     */
    public static ByteBuf encode(int fixedBlockSize, DataPacket packet) {
        int size = 12; // Fixed width
        if (packet.hasExtension()) {
            size += 4 + packet.getExtensionDataSize();
        }
        size += packet.getContributingSourcesCount() * 4;
        size += packet.getDataSize();

        // If packet was configured to have padding (fixed block size), calculate padding and add it.
        int padding = 0;
        if (fixedBlockSize > 0) {
            // If padding modulus is > 0 then the padding is equal to:
            // (global size of the compound RTCP packet) mod (block size)
            // Block size alignment might be necessary for some encryption algorithms
            // RFC section 6.4.1
            padding = fixedBlockSize - (size % fixedBlockSize);
            if (padding == fixedBlockSize) {
                padding = 0;
            }
        }
        size += padding;

        ByteBuf buffer = Unpooled.buffer(size);

        // Version, Padding, eXtension, CSRC Count
        byte b = packet.getVersion().getByte();
        if (padding > 0) {
            b |= 0x20; // 0010 0000
        }
        if (packet.hasExtension()) {
            b |= 0x10; // 0001 0000
        }
        b |= packet.getContributingSourcesCount();
        buffer.writeByte(b);

        // Marker, Payload Type
        b = (byte) packet.getPayloadType();
        if (packet.hasMarker()) {
            b |= 0x80; // 1000 0000
        }
        buffer.writeByte(b);

        buffer.writeShort(packet.sequenceNumber);
        buffer.writeInt((int) packet.timestamp);
        buffer.writeInt((int) packet.ssrc);

        // Write extension headers & data
        if (packet.hasExtension()) {
            buffer.writeShort(packet.extensionHeaderData);
            buffer.writeShort(packet.extensionData.length / 4);
            buffer.writeBytes(packet.extensionData);
        }

        // Write CCRC's
        if (packet.getContributingSourcesCount() > 0) {
            for (Long contributingSourceId : packet.getContributingSourceIds()) {
                buffer.writeInt(contributingSourceId.intValue());
            }
        }

        // Write RTP data
        if (packet.data != null) {
            buffer.writeBytes(packet.data.array());
        }

        if (padding > 0) {
            // Final bytes: padding
            for (int i = 0; i < (padding - 1); i++) {
                buffer.writeByte(0x00);
            }

            // Final byte: the amount of padding bytes that should be discarded.
            // Unless something's wrong, it will be a multiple of 4.
            buffer.writeByte(padding);
        }

        return buffer;
    }

    // public methods -------------------------------------------------------------------------------------------------
    /**
     * Encodes this DataPacket.
     * 
     * @param fixedBlockSize set this param, if the packet should have a fixed block size (have a padding)
     * @return a {@link ByteBuf} containing the bytes
     */
    public ByteBuf encode(int fixedBlockSize) {
        return encode(fixedBlockSize, this);
    }

    /**
     * Encodes this DataPacket. Assume that no fixed block size should be used.
     * 
     * @return a {@link ByteBuf} containing the bytes
     */
    public ByteBuf encode() {
        return encode(0, this);
    }

    public void addContributingSourceId(long contributingSourceId) {
        if (this.contributingSourceIds == null) {
            this.contributingSourceIds = new ArrayList<Long>();
        }

        this.contributingSourceIds.add(contributingSourceId);
    }

    public int getDataSize() {
        if (this.data == null) {
            return 0;
        }

        return this.data.capacity();
    }

    public int getExtensionDataSize() {
        if (this.extensionData == null) {
            return 0;
        }

        return this.extensionData.length;
    }

    public int getContributingSourcesCount() {
        if (this.contributingSourceIds == null) {
            return 0;
        }

        return this.contributingSourceIds.size();
    }

    public void setExtensionHeader(short extensionHeaderData, byte[] extensionData) {
        if (extensionData.length > 65536) {
            throw new IllegalArgumentException("Extension data cannot exceed 65536 bytes");
        }
        if ((extensionData.length % 4) != 0) {
            throw new IllegalArgumentException("Extension data must be one or more 32-bit words.");
        }
        this.extensionHeaderData = extensionHeaderData;
        this.extensionData = extensionData;
    }

    // getters & setters ----------------------------------------------------------------------------------------------
    public RtpVersion getVersion() {
        return version;
    }

    /**
     * 
     * @param version Only {@link RtpVersion}.V2 is supported!
     */
    public void setVersion(RtpVersion version) {
        if (version != RtpVersion.V2) {
            throw new IllegalArgumentException("Only V2 is supported");
        }
        this.version = version;
    }

    public boolean hasExtension() {
        return this.extensionData != null;
    }

    public boolean hasMarker() {
        return marker;
    }

    public void setMarker(boolean marker) {
        this.marker = marker;
    }

    public int getPayloadType() {
        return payloadType;
    }

    /**
     * Sets the payload type.
     * 
     * @param payloadType number between 0 and 127
     */
    public void setPayloadType(int payloadType) {
        if ((payloadType < 0) || (payloadType > 127)) {
            throw new IllegalArgumentException("PayloadType must be in range [0;127]");
        }
        this.payloadType = payloadType;
    }

    public int getSequenceNumber() {
        return sequenceNumber;
    }

    public void setSequenceNumber(int sequenceNumber) {
        this.sequenceNumber = sequenceNumber;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }

    public long getSsrc() {
        return ssrc;
    }

    public void setSsrc(long ssrc) {
        if ((ssrc < 0) || (ssrc > 0xffffffffL)) {
            throw new IllegalArgumentException("Valid range for SSRC is [0;0xffffffff]");
        }
        this.ssrc = ssrc;
    }

    public short getExtensionHeaderData() {
        return extensionHeaderData;
    }

    public byte[] getExtensionData() {
        return extensionData;
    }

    public List<Long> getContributingSourceIds() {
        return contributingSourceIds;
    }

    public void setContributingSourceIds(List<Long> contributingSourceIds) {
        this.contributingSourceIds = contributingSourceIds;
    }

    public ByteBuf getData() {
        return data;
    }

    public void setData(ByteBuf data) {
        this.data = data;
    }

    public byte[] getDataAsArray() {
        return this.data.array();
    }

    public void setData(byte[] data) {
        this.data = Unpooled.wrappedBuffer(data);
    }

    // low level overrides --------------------------------------------------------------------------------------------
    @Override
    public String toString() {
        return new StringBuilder().append("DataPacket{V=").append(this.version).append(", X=")
                .append(this.hasExtension()).append(", CC=").append(this.getContributingSourcesCount())
                .append(", M=").append(this.marker).append(", PT=").append(this.payloadType).append(", SN=")
                .append(this.sequenceNumber).append(", TS=").append(this.timestamp).append(", SSRC=")
                .append(this.ssrc).append(", CSRCs=").append(this.contributingSourceIds).append(", data=")
                .append(this.getDataSize()).append(" bytes}").toString();
    }
}