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] */