package javaforce.voip; import java.util.*; import java.net.*; import javaforce.*; import javaforce.media.*; public class RTPChannel { private int seqnum = 0; private int timestamp = 0; protected int ssrc_src = -1, ssrc_dst = -1; private static Random r = new Random(); private static Object rlock = new Object(); private short dtmfduration = 0; protected short turn1ch, turn2ch; //dynnamic codec payload ids private int rfc2833_id = -1; private int vp8_id = -1; private int h264_id = -1; private int h263_1998_id = -1; private int h263_2000_id = -1; private char dtmfChar; private boolean dtmfSent = false; private AudioBuffer buffer = new AudioBuffer(8000, 1, 2); //freq, chs, seconds private DTMF dtmf; private static short silence8[] = new short[160]; private static short silence16[] = new short[320]; protected long turnBindExpires; private long lastPacket = 0; protected boolean active = false; private MusicOnHold moh = new MusicOnHold(); public RTP rtp; public SDP.Stream stream; public Coder coder_g711u, coder_g711a, coder_g722, coder_g729a; public Coder coder; //selected audio encoder protected RTPChannel(RTP rtp, int ssrc, SDP.Stream stream) { this.rtp = rtp; this.ssrc_src = ssrc; this.stream = stream; } public int getVP8id() { return vp8_id; } public int getH264id() { return h264_id; } public int getH263_1998id() { return h263_1998_id; } public int getH263_2000id() { return h263_2000_id; } public int getRFC2833id() { return rfc2833_id; } /** * Writes a packet to the RTP port. */ public void writeRTP(byte data[], int off, int len) { if (!rtp.active) { JFLog.log("RTPChannel.writeRTP() : not active"); return; } if (stream.getPort() == -1) { JFLog.log("RTPChannel.writeRTP() : not ready (NATing)"); return; //not ready yet (NATing) } if (!stream.canSend()) { JFLog.log("RTPChannel.writeRTP() : stream without send"); return; } try { if (rtp.useTURN) { rtp.stun1.sendData(turn1ch, data, off, len); } else { rtp.sock1.send(new DatagramPacket(data, off, len, InetAddress.getByName(stream.getIP()), stream.getPort())); } } catch (Exception e) { JFLog.log(e); } } /** * Writes a packet to the RTCP port. */ public void writeRTCP(byte data[], int off, int len) { if (!rtp.active) { return; } if (stream.getPort() == -1) { return; //not ready yet (NATing) } try { if (rtp.useTURN) { rtp.stun2.sendData(turn2ch, data, off, len); } else { rtp.sock2.send(new DatagramPacket(data, off, len, InetAddress.getByName(stream.getIP()), stream.getPort() + 1)); } } catch (Exception e) { JFLog.log("err:RTP.writeRTCP:failed"); JFLog.log(e); } } /** * Writes a RFC 2833 (DTMF) RTP packet. */ public void writeDTMF(char digit, boolean end) { byte data[] = new byte[16]; buildHeader(data, RTP.CODEC_RFC2833.id, getseqnum(), gettimestamp(160), getssrc(), false); switch (digit) { case '*': data[12] = 10; break; case '#': data[12] = 11; break; default: data[12] = (byte) (digit - '0'); break; } if (end) { data[13] = (byte) 0x8a; //volume=10 + end of packet } else { data[13] = 0x0a; //volume=10 } data[14] = (byte) ((dtmfduration & 0xff00) >> 8); data[15] = (byte) (dtmfduration & 0xff); dtmfduration += 160; writeRTP(data, 0, data.length); if (end) { //send 'end of DTMF' 3 times to ensure it's received writeRTP(data, 0, data.length); writeRTP(data, 0, data.length); dtmfduration = 0; } } /** * Builds RTP header in first 12 bytes of data[]. */ public static void buildHeader(byte data[], int id, int seqnum, int timestamp, int ssrc, boolean last) { //build RTP header data[0] = (byte) 0x80; //version data[1] = (byte) id; //0=g711u 8=g711a 18=g729a 26=JPEG 34=H.263 etc. if (last) { data[1] |= 0x80; } BE.setuint16(data, 2, seqnum); BE.setuint32(data, 4, timestamp); BE.setuint32(data, 8, ssrc); } public void buildHeader(byte data[], int type) { buildHeader(data, type, 0, 0, getssrc(), false); } public int getseqnum() { return seqnum++; } public int gettimestamp(int delta) { int ret = timestamp; timestamp += delta; return ret; } public int getssrc() { if (ssrc_src != -1) { return ssrc_src; } synchronized (rlock) { ssrc_src = r.nextInt() & 0x7fffffff; } return ssrc_src; } public static int getseqnum(byte[] data, int off) { return BE.getuint16(data, 2 + off); } public static int gettimestamp(byte[] data, int off) { return BE.getuint32(data, 4 + off); } public static int getssrc(byte[] data, int off) { return BE.getuint32(data, 8 + off); } public String getremoteip() { if (rtp.useTURN) { return rtp.stun1.getIP(); } else { return stream.getIP(); } } public int getremoteport() { return stream.getPort(); } public boolean start() { lastPacket = System.currentTimeMillis(); JFLog.log("RTPChannel.start() : localhost:" + rtp.getlocalrtpport() + " remote=" + stream.getIP() + ":" + stream.getPort()); if ((stream.codecs != null) && (stream.codecs.length > 0)) { Codec codec_rfc2833 = stream.getCodec(RTP.CODEC_RFC2833); if (codec_rfc2833 != null) { rfc2833_id = codec_rfc2833.id; } Codec codec_vp8 = stream.getCodec(RTP.CODEC_VP8); if (codec_vp8 != null) { vp8_id = codec_vp8.id; } Codec codec_h264 = stream.getCodec(RTP.CODEC_H264); if (codec_h264 != null) { h264_id = codec_h264.id; } Codec codec_h263_1998 = stream.getCodec(RTP.CODEC_H263_1998); if (codec_h263_1998 != null) { h263_1998_id = codec_h263_1998.id; } Codec codec_h263_2000 = stream.getCodec(RTP.CODEC_H263_2000); if (codec_h263_2000 != null) { h263_2000_id = codec_h263_2000.id; } coder_g711u = new g711u(rtp); coder_g711a = new g711a(rtp); coder_g722 = new g722(rtp); coder_g729a = new g729a(rtp); if (stream.type == SDP.Type.audio) { //must use first available codec coder = null; for(int a=0;a<stream.codecs.length;a++) { Codec codec = stream.codecs[a]; if (codec.equals(RTP.CODEC_G711u)) { coder = coder_g711u; JFLog.log("codec = g711u"); break; } else if (codec.equals(RTP.CODEC_G711a)) { coder = coder_g711a; JFLog.log("codec = g711a"); break; } else if (codec.equals(RTP.CODEC_G722)) { coder = coder_g722; JFLog.log("codec = g722"); break; } else if (codec.equals(RTP.CODEC_G729a)) { coder = coder_g729a; JFLog.log("codec = g729a"); break; } } if (coder == null) { JFLog.log("RTP.start() : Warning : no compatible audio codec selected"); } dtmf = new DTMF(coder.getSampleRate()); } else { boolean haveVcodec = false; for(int a=0;a<stream.codecs.length;a++) { Codec codec = stream.codecs[a]; if (codec.equals(RTP.CODEC_H263)) { JFLog.log("codec = H.263"); break; } else if (codec.equals(RTP.CODEC_H263_1998)) { JFLog.log("codec = H.263-1998"); break; } else if (codec.equals(RTP.CODEC_H263_2000)) { JFLog.log("codec = H.263-2000"); break; } else if (codec.equals(RTP.CODEC_JPEG)) { JFLog.log("codec = JPEG"); break; } else if (codec.equals(RTP.CODEC_H264)) { JFLog.log("codec = H.264"); break; } else if (codec.equals(RTP.CODEC_VP8)) { JFLog.log("codec = VP8"); break; } } if (!haveVcodec) { JFLog.log("RTP.start() : Warning : no compatible video codec selected"); } } } else { JFLog.log("RTP:Error:No codecs provided"); } if (rtp.useTURN) { try { synchronized (rtp.bindLock) { rtp.bindingChannel = this; turn1ch = rtp.getNextTURNChannel(); rtp.wait4reset(); rtp.stun1.requestBind(turn1ch, stream.getIP(), stream.getPort()); rtp.wait4reply(); turn2ch = rtp.getNextTURNChannel(); rtp.wait4reset(); rtp.stun2.requestBind(turn2ch, stream.getIP(), stream.getPort() + 1); rtp.wait4reply(); } } catch (Exception e) { JFLog.log(e); return false; } } active = true; return true; } /** * Changes the SDP.Stream for this RTP session. Could occur in a SIP reINVITE. */ public boolean change(SDP.Stream new_stream) { lastPacket = System.currentTimeMillis(); stream = new_stream; if (stream.type == SDP.Type.audio) { if (new_stream.hasCodec(RTP.CODEC_G711u)) { coder = coder_g711u; } else if (new_stream.hasCodec(RTP.CODEC_G711a)) { coder = coder_g711a; } else if (new_stream.hasCodec(RTP.CODEC_G722)) { coder = coder_g722; } else if (new_stream.hasCodec(RTP.CODEC_G729a)) { coder = coder_g729a; } dtmf = new DTMF(coder.getSampleRate()); } if (rtp.useTURN) { synchronized (rtp.bindLock) { rtp.bindingChannel = this; rtp.stun1.requestBind(turn1ch, stream.getIP(), stream.getPort()); rtp.stun2.requestBind(turn2ch, stream.getIP(), stream.getPort() + 1); } } return true; } protected void processRTP(byte data[], int off, int len) { lastPacket = RTP.now; if (rtp.rawMode) { rtp.iface.rtpPacket(this, data, off, len); return; } int id = data[off + 1] & 0x7f; //payload id if (id < 96) { switch (id) { case 0: dtmfSent = false; //just in case end of dtmf was not received if (len != 160 + 12) { JFLog.log("RTP:Bad g711u length"); break; } addSamples(coder_g711u.decode(data, off)); rtp.iface.rtpSamples(this); break; case 8: dtmfSent = false; //just in case end of dtmf was not received if (len != 160 + 12) { JFLog.log("RTP:Bad g711a length"); break; } addSamples(coder_g711a.decode(data, off)); rtp.iface.rtpSamples(this); break; case 9: dtmfSent = false; //just in case end of dtmf was not received if (len != 160 + 12) { JFLog.log("RTP:Bad g722 length"); break; } addSamples(coder_g722.decode(data, off)); rtp.iface.rtpSamples(this); break; case 18: dtmfSent = false; //just in case end of dtmf was not received if (len != 20 + 12) { JFLog.log("RTP:Bad g729a length"); break; } addSamples(coder_g729a.decode(data, off)); rtp.iface.rtpSamples(this); break; case 26: rtp.iface.rtpJPEG(this, data, off, len); break; case 34: rtp.iface.rtpH263(this, data, off, len); break; } } else { if (id == rfc2833_id) { dtmfChar = ' '; if ((data[off + 12] >= 0) && (data[off + 12] <= 9)) { dtmfChar = (char) ('0' + data[off + 12]); } if (data[off + 12] == 10) { dtmfChar = '*'; } if (data[off + 12] == 11) { dtmfChar = '#'; } if (data[off + 13] < 0) { //0x80 == end of dtmf dtmfSent = false; dtmfChar = ' '; } if (dtmfChar == ' ') { switch (coder.getSampleRate()) { case 8000: addSamples(silence8); break; case 16000: addSamples(silence16); break; } } else { addSamples(dtmf.getSamples(dtmfChar)); if (!dtmfSent) { rtp.iface.rtpDigit(this, dtmfChar); dtmfSent = true; } } } else if (id == vp8_id) { rtp.iface.rtpVP8(this, data, off, len); } else if (id == h264_id) { rtp.iface.rtpH264(this, data, off, len); } else if (id == h263_1998_id) { rtp.iface.rtpH263_1998(this, data, off, len); } else if (id == h263_2000_id) { rtp.iface.rtpH263_2000(this, data, off, len); } } } protected void processRTCP(byte data[], int off, int len) { if (rtp.rawMode) { rtp.iface.rtcpPacket(this, data, off, len); } //TODO : RTCP ??? } protected void keepalive(long now) { //do refreshes a little sooner (75 seconds) (in case nonce changes) if (rtp.useTURN && (now + 75 * 1000) > turnBindExpires) { //request another 10 mins (actually just 5???) synchronized (rtp.bindLock) { rtp.bindingChannel = this; rtp.stun1.requestBind(turn1ch, stream.getIP(), stream.getPort()); rtp.stun2.requestBind(turn2ch, stream.getIP(), stream.getPort() + 1); } } if (active && stream.type == SDP.Type.audio && stream.canRecv() && (now - 45 * 1000) > lastPacket) { rtp.iface.rtpInactive(this); } } /** * Returns a packet of decoded samples. */ public boolean getSamples(short data[]) { if (!stream.canRecv()) { return moh.getSamples(data); } return buffer.get(data, 0, data.length); } private void addSamples(short data[]) { buffer.add(data, 0, data.length); } public String toString() { return "RTPChannel:{src=" + ssrc_src + ",dst=" + ssrc_dst + ",ip=" + stream.getIP() + ":" + stream.getPort() + "}"; } }