/*
* Copyright 2007 Sun Microsystems, Inc.
*
* This file is part of jVoiceBridge.
*
* jVoiceBridge is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation and distributed hereunder
* to you.
*
* jVoiceBridge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied this
* code.
*/
package com.sun.voip;
import java.net.DatagramPacket;
import java.net.SocketAddress;
/**
* Definitions for RTP data packets. See RFC1889 for more details.
*
*
* The RTP header has the following format:
*
* 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 |
* | .... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
*/
public class RtpPacket {
/**
* RTP Header size in bytes
*/
public static final int HEADER_SIZE = 12;
/**
* Offset to start of data
*/
public static final int DATA = HEADER_SIZE;
/*
* Header extension bit
*/
public static final byte X_BIT = (byte)0x10;
/**
* Payload change to indicate comfort noise. This is the
* low 7 bits of byte 1 of the RTP packet.
*/
public static final byte COMFORT_PAYLOAD = (byte)0xd;
/**
* Payload change to indicate ulaw data. This is the
* low 7 bits of byte 1 of the RTP packet.
*/
public static final byte PCMU_PAYLOAD = (byte)0;
/**
* Change Media payload
*/
public static final byte CHANGE_MEDIA_PAYLOAD = 127; // XXX
/**
* Flag to indicate a significant event such as an unexpected
* change in the sequence number after resuming sending data
* after comfort noise.
*/
public static final byte MARK_BIT = (byte)0x80;
/**
* Flag to indicate dtmf key has been released.
*/
public static final byte DTMF_END_BIT = (byte)0x80;
/**
* Level of comfort noise to generate
*/
public static byte defaultNoiseLevel = (byte)62;
public static byte comfortNoiseLevel = defaultNoiseLevel;
/**
* Number of milliseconds between RTP packets.
*/
public final static int PACKET_PERIOD = 20;
/**
* Number of packets per second
*/
public final static int PACKETS_PER_SECOND = 1000 / PACKET_PERIOD;
/**
* Number of bytes per sample for ULAW data
*/
public final static int PCMU_SAMPLE_SIZE = 1;
/**
* Number of bytes per sample for linear data
*/
public final static int PCM_SAMPLE_SIZE = 2;
/**
* Encoding values
*/
public final static int PCMU_ENCODING = 0;
public final static int PCM_ENCODING = 1;
public final static int SPEEX_ENCODING = 2;
public final static int MAX_SAMPLE_RATE = 48000;
public final static int MAX_CHANNELS = 2;
public final static int MAC_SAMPLE_RATE = 44100;
public final static int MAC_CHANNELS = 2;
/*
* It would have been nicer to extend DatagramPacket rather than to
* have a DatagramPacket here. Then the DatagramPacket routines wouldn't
* have to be duplicated below. However, DatagramPacket is final
* and can't be extended.
*/
protected DatagramPacket packet;
protected byte[] buffer;
protected int bufferSize; // total size of buffer
protected int dataSize; // data size for PACKET_PERIOD
protected short rtpSequenceNumber = 1;
protected long rtpTimestamp = 0;
protected int samplesPerPacket;
protected int size; // actual size of data in this packet now
protected int encoding;
protected int sampleRate;
protected int channels;
public RtpPacket(byte[] buffer) {
this.buffer = buffer;
bufferSize = buffer.length;
}
public RtpPacket(int encoding, int sampleRate, int channels) {
this(encoding, sampleRate, channels,
HEADER_SIZE + getDataSize(encoding, sampleRate, channels));
}
public RtpPacket(int encoding, int sampleRate, int channels,
int bufferSize) {
this.encoding = encoding;
this.sampleRate = sampleRate;
this.channels = channels;
this.bufferSize = bufferSize;
//System.out.println("RtpPacket: "
// + (encoding == RtpPacket.PCMU_ENCODING ? "PCMU/" : "PCM/")
// + sampleRate + "/" + channels + "/" + encoding
// + " bufferSize " + bufferSize);
buffer = new byte[bufferSize];
packet = new DatagramPacket(buffer, bufferSize);
dataSize = getDataSize(encoding, sampleRate, channels);
}
public int getDataSize() {
return getDataSize(encoding, sampleRate, channels);
}
public static int getMaxDataSize() {
return getDataSize(PCM_ENCODING, MAX_SAMPLE_RATE, 2);
}
public static int getDataSize(int encoding, int sampleRate, int channels) {
int dataSize;
String e;
int samplesPerPacket = (sampleRate * channels) / PACKETS_PER_SECOND;
if (encoding == PCMU_ENCODING) {
dataSize = samplesPerPacket * PCMU_SAMPLE_SIZE;
e = "ULAW";
} else if (encoding == PCM_ENCODING) {
dataSize = samplesPerPacket * PCM_SAMPLE_SIZE;
e = "PCM";
} else {
dataSize = samplesPerPacket * PCM_SAMPLE_SIZE;
e = "SPEEX";
}
if (Logger.logLevel >= Logger.LOG_DEBUG) {
Logger.writeFile("Packet data size is " + dataSize
+ " for " + e + "/" + sampleRate + "/" + channels);
}
return dataSize;
}
/**
* Get number of samples in each packet
*/
public int getSamplesPerPacket(int sampleRate, int channels) {
return (sampleRate * channels) / PACKETS_PER_SECOND;
}
/*
* Set X bit.
*/
public void setX(boolean x) {
if (x == true) {
buffer[0] |= X_BIT;
} else {
buffer[0] &= ~X_BIT;
}
}
public boolean getX() {
return (buffer[0] & X_BIT) != 0;
}
/**
* Get the RTP Payload type.
* @return payload byte paylod
*/
public byte getRtpPayload() {
return (byte)(buffer[1] & ~MARK_BIT);
}
/**
* Set the RTP Payload type.
* @param payload byte payload
*/
public void setRtpPayload(byte payload) {
byte mark = (byte)(buffer[1] & MARK_BIT);
buffer[1] = (byte)(payload | mark);
}
/**
* Set the MARK bit.
*/
public void setMark() {
buffer[1] |= MARK_BIT;
}
public void clearMark() {
buffer[1] &= ~MARK_BIT;
}
/**
* Determine if the MARK bit is set.
* @return isMarkSet boolean true if set
*/
public boolean isMarkSet() {
return (buffer[1] & MARK_BIT) != 0;
}
/**
* Determine if the DTMF_END_BIT is set.
* @return isDtmfSet boolean true if set
*/
public boolean isDtmfEndSet() {
return (buffer[DATA + 1] & DTMF_END_BIT) != 0;
}
/**
* Get RTP sequence number
* @return sequence number
*/
public short getRtpSequenceNumber() {
return (short)
(((buffer[2] << 8) & 0xff00) |
(buffer[3]) & 0xff);
}
public void setRtpSequenceNumber(short rtpSequenceNumber) {
buffer[2] = (byte) ((rtpSequenceNumber >> 8) & 0xff);
buffer[3] = (byte) (rtpSequenceNumber & 0xff);
}
/**
* Get RTP timestamp
* @return RTP timestamp
*/
public long getRtpTimestamp() {
long ts = (long)(((((long)(buffer[4] & 0xff)) << 24) & 0xff000000)
| ((((long)(buffer[5] & 0xff)) << 16) & 0x00ff0000)
| ((((long)(buffer[6] & 0xff)) << 8) & 0x0000ff00)
| ((long)(buffer[7] & 0xff))) & 0xffffffff;
return ts;
}
public void setRtpTimestamp(int rtpTimestamp) {
buffer[4] = (byte) ((rtpTimestamp >> 24) & 0xff);
buffer[5] = (byte) ((rtpTimestamp >> 16) & 0xff);
buffer[6] = (byte) ((rtpTimestamp >> 8) & 0xff);
buffer[7] = (byte) (rtpTimestamp & 0xff);
}
public int getSynchronizationSource() {
int ss = (int)(((((int)(buffer[8] & 0xff)) << 24) & 0xff000000)
| ((((int)(buffer[9] & 0xff)) << 16) & 0x00ff0000)
| ((((int)(buffer[10] & 0xff)) << 8) & 0x0000ff00)
| ((int)(buffer[11] & 0xff))) & 0xffffffff;
return ss;
}
public void setSynchronizationSource(int synchronizationSource) {
buffer[8] = (byte) ((synchronizationSource >> 24) & 0xff);
buffer[9] = (byte) ((synchronizationSource >> 16) & 0xff);
buffer[10] = (byte) ((synchronizationSource >> 8) & 0xff);
buffer[11] = (byte) (synchronizationSource & 0xff);
}
/**
* Get the comfort noise level
*/
public byte getComfortNoiseLevel() {
return buffer[DATA];
}
/**
* Get the packet data buffer
* @return data buffer byte array data buffer
*/
public byte[] getData() {
return packet.getData();
}
/**
* Set the packet data buffer
*/
public void setBuffer(byte[] buffer) {
this.buffer = buffer;
packet.setData(buffer);
}
/**
* Get the DatagramPacket.
* @return datagramPacket DatagramPacket
*/
public DatagramPacket getDatagramPacket() {
return packet;
}
/**
* Get data buffer length.
* @ return length int length
*/
public int getLength() {
return packet.getLength();
}
public int getLinearLength() {
if (encoding == PCMU_ENCODING) {
return ((packet.getLength() - HEADER_SIZE) * 2) + HEADER_SIZE;
}
return packet.getLength();
}
/**
* Get the SocketAddress of the sender of this packet.
* @return socketAddress SocketAddress
*/
public SocketAddress getSocketAddress() {
return packet.getSocketAddress();
}
/**
* Set the length of data to send
* @param size int size
*/
public void setLength(int size) {
this.size = size;
packet.setLength(size);
}
/**
* Set the SocketAddress of where to send this packet.
*/
public void setSocketAddress(SocketAddress socketAddress) {
packet.setSocketAddress(socketAddress);
}
/**
* Translate comfort noise level to a multiplier for the volume level.
*
* The volume level is used to adjust the volume of the comfort noise
* we play to a HeadSet.
*
* The comfort noise level is a byte with values from 0 to 127
* representing 0.
*
* We have arbitrarily assigned the value 64 to be a volumeLevel of 1.0
* w.r.t. the comfort noise audio file that we use to generate
* comfort noise.
*
* Values smaller than 64 increase volume; values bigger than 64
* reduce volume. A value of 0 increases the volume by 4.0.
* A value of 127 reduces the volume to 1/4.
*
* Feel free to adjust these values!
*/
public static double getVolumeLevel(byte comfortNoiseLevel) {
if (comfortNoiseLevel < 0 ||
comfortNoiseLevel == defaultNoiseLevel) {
return 1.0F;
}
double volumeLevel;
if (comfortNoiseLevel > defaultNoiseLevel) {
volumeLevel = ((double)defaultNoiseLevel - comfortNoiseLevel - 1) /
defaultNoiseLevel / 4;
} else {
volumeLevel = ((double)defaultNoiseLevel - comfortNoiseLevel) /
defaultNoiseLevel * 4;
}
return volumeLevel;
}
/**
* Set the default comfort noise value.
*/
public static void setDefaultComfortNoiseLevel(byte comfortNoiseLevel) {
RtpPacket.comfortNoiseLevel = comfortNoiseLevel;
}
/**
* Get the default comfort noise volume level.
*/
public static byte getDefaultComfortNoiseLevel() {
return comfortNoiseLevel;
}
}