package javaforce.voip;
/**
* Encodes/Decodes RTP/H264 packets
*
* http://tools.ietf.org/html/rfc3984
*
* @author pquiring
*/
import java.util.*;
import javaforce.*;
public class RTPH264 {
public RTPH264() {
ssrc = new Random().nextInt();
}
private int find_best_length(byte data[], int offset, int length) {
//see if there is a 0,0,1 and return a length to that
//this way the next packet will start at a resync point
for(int a=1;a<length-3;a++) {
if (data[offset + a] == 0 && data[offset + a + 1] == 0 && data[offset + a + 2] == 1) return a;
}
return length;
}
/*
* NAL Header : F(1) NRI(2) TYPE(5) : F=0 NRI=0-3 TYPE=1-23:full_packet 28=FU-A
* FUA Header : S(1) E(1) R(1) TYPE(5) : S=start E=end R=reserved TYPE=???
*/
/** Encodes raw H.264 data into multiple RTP packets. */
public byte[][] encode(byte data[], int x, int y, int id) {
ArrayList<byte[]> packets = new ArrayList<byte[]>();
int len = data.length;
int packetLength;
int offset = 0;
byte packet[];
while (len > 0) {
//skip 0,0,1
while (data[offset] == 0) {offset++; len--;}
offset++; len--; //skip 1
if (len > mtu) {
packetLength = find_best_length(data, offset, len);
} else {
packetLength = len;
}
if (packetLength > mtu) {
//need to split up into Frag Units (mode A)
int nalLength = mtu - 2;
byte type = (byte)(data[offset] & 0x1f);
byte nri = (byte)(data[offset] & 0x60);
offset++;
len--;
packetLength--;
boolean first = true;
while (packetLength > nalLength) {
packet = new byte[12 + 2 + nalLength];
RTPChannel.buildHeader(packet, id, seqnum++, timestamp, ssrc, false);
packet[12] = 28; //FU-A
packet[12] |= nri;
packet[13] = type;
if (first) {
packet[13] |= 0x80; //first FU packet
first = false;
}
System.arraycopy(data, offset, packet, 14, nalLength);
offset += nalLength;
len -= nalLength;
packetLength -= nalLength;
packets.add(packet);
}
//add last NAL packet
nalLength = packetLength;
packet = new byte[12 + 2 + nalLength];
RTPChannel.buildHeader(packet, id, seqnum++, timestamp, ssrc, len == nalLength);
packet[12] = 28; //F=0 TYPE=28 (FU-A)
packet[12] |= nri;
packet[13] = type;
packet[13] |= 0x40; //last FU packet
System.arraycopy(data, offset, packet, 14, nalLength);
offset += nalLength;
len -= nalLength;
packetLength -= nalLength;
packets.add(packet);
} else {
packet = new byte[packetLength + 12]; //12=RTP.length
RTPChannel.buildHeader(packet, id, seqnum++, timestamp, ssrc, len == packetLength);
System.arraycopy(data, offset, packet, 12, packetLength);
packets.add(packet);
offset += packetLength;
len -= packetLength;
}
}
timestamp += 100; //??? 10 fps ???
return packets.toArray(new byte[0][0]);
}
/**
* Returns last full packet.
*/
public byte[] decode(byte rtp[]) {
if (rtp.length < 12 + 2) return null; //bad packet
int h264Length = rtp.length - 12;
int type = rtp[12] & 0x1f;
if (partial == null) {
partial = new byte[0];
}
if (type >= 1 && type <= 23) {
//a full packet
int partialLength = partial.length;
partial = JF.copyOf(partial, partial.length + 4 + h264Length);
partial[partialLength + 3] = 1; //0,0,0,1
System.arraycopy(rtp, 12, partial, partialLength + 4, h264Length);
} else if (type == 28) {
//FU-A Packet
if ((rtp[13] & 0x80) == 0x80) {
//first NAL packet (restore first byte)
int nri = rtp[12] & 0x60;
type = rtp[13] & 0x1f;
partial = JF.copyOf(partial, partial.length + 5);
partial[partial.length-2] = 1; //0,0,0,1
partial[partial.length-1] = (byte)(nri + type); //NRI TYPE (first byte)
lastseqnum = RTPChannel.getseqnum(rtp, 0);
} else {
int thisseqnum = RTPChannel.getseqnum(rtp, 0);
if (thisseqnum != lastseqnum + 1) {
JFLog.log("H264:Received FU-A packet out of order, discarding frame.");
partial = null;
lastseqnum = -1;
return null;
}
lastseqnum = thisseqnum;
}
if ((rtp[13] & 0x40) == 0x40) {
//last NAL packet
lastseqnum = -1;
}
int partialLength = partial.length;
h264Length -= 2;
partial = JF.copyOf(partial, partial.length + h264Length);
System.arraycopy(rtp, 12+2, partial, partialLength, h264Length);
} else {
JFLog.log("H264:Unsupported packet type:" + type);
partial = null;
lastseqnum = -1;
return null;
}
if ((rtp[1] & 0x80) == 0x80) { //check RTP.M flag
byte full[] = partial;
partial = null;
lastseqnum = -1;
return full;
}
return null;
}
//mtu = 1500 - 14(ethernet) - 20(ip) - 8(udp) - 12(rtp) = 1446 bytes payload per packet
private static final int mtu = 1446;
private int seqnum;
private int timestamp;
private final int ssrc;
private byte partial[];
private int lastseqnum = -1;
}
/*
Type Name
0 [invalid]
1 Coded slice
2 Data Partition A
3 Data Partition B
4 Data Partition C
5 IDR (Instantaneous Decoding Refresh) Picture
6 SEI (Supplemental Enhancement Information)
7 SPS (Sequence Parameter Set)
8 PPS (Picture Parameter Set)
9 Access Unit Delimiter
10 EoS (End of Sequence)
11 EoS (End of Stream)
12 Filter Data
13-23 [extended]
24-31 [unspecified]
*/