/* * 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.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Vector; import java.text.ParseException; public class SdpParser { private SdpInfo sdpInfo; private Vector supportedMedia = new Vector(); public SdpParser() { } public synchronized SdpInfo parseSdp(String sdpData) throws ParseException { /* * parse sdpData */ String t = "c=IN IP4 "; int start = sdpData.indexOf(t); int finish = sdpData.indexOf("\r", start); if (start < 0 || finish < 0) { throw new ParseException("Invalid remote SDP", 0); } String remoteHost = sdpData.substring(start + t.length(), finish).trim(); t = "m=audio"; start = sdpData.indexOf(t); String s = sdpData.substring(start + t.length()); t = "RTP/AVP "; finish = s.indexOf(t); String port = s.substring(0, finish).trim(); if (start < 0 || finish < 0) { throw new ParseException("Invalid remote SDP", 0); } int remotePort; try { remotePort = Integer.parseInt(port); } catch (NumberFormatException e) { Logger.println("Invalid remote port in sdp " + port + " sdpData " + sdpData); throw new ParseException("Invalid remote port in sdp " + port + " sdpData " + sdpData, 0); } start = finish + t.length(); finish = s.indexOf("\r\n"); s = s.substring(start, finish); // point at payloads /* * Get all supported RTP Payloads */ String[] payloads = s.split("[\" \"]"); String[] sdp = sdpData.split("[\r\n]"); MediaInfo mediaInfo = new MediaInfo(RtpPacket.PCMU_PAYLOAD, 0, 8000, 1, false); supportedMedia.add(mediaInfo); // we always support payload 0 byte telephoneEventPayload = 0; /* * Get all "a=rtpmap:" entries, stop when we hit a non-rtpmap entry */ for (int i = 0 ; i < sdp.length; i++) { s = sdp[i]; if (s.indexOf("a=rtpmap:") != 0) { continue; } RtpmapParser rtpmapParser = new RtpmapParser(s); mediaInfo = rtpmapParser.getMediaInfo(); if (mediaInfo == null) { //Logger.println("no media info for " + s); continue; // skip this entry } if (mediaInfo.isTelephoneEventPayload()) { telephoneEventPayload = mediaInfo.getPayload(); } supportedMedia.add(mediaInfo); } /* * At this point, payloads[] contains all of the supported payloads * and the Vector supportedMedia contains the MediaInfo's for * all supported payloads. * * For each payload, find the corresponding MediaInfo and * select the appropriate one. */ mediaInfo = null; boolean preferredMediaSpecified = false; t = "a=PreferredPayload:"; if ((start = sdpData.indexOf(t)) >= 0) { s = sdpData.substring(start + t.length()); finish = s.indexOf("\r\n"); if (finish > 0) { int payload; s = s.substring(0, finish); payload = Integer.parseInt(s); try { mediaInfo = getMediaInfo(payload); } catch (ParseException e) { } preferredMediaSpecified = true; } } if (mediaInfo == null) { for (int i = 0; i < payloads.length; i++) { int payload = 0; try { payload = Integer.parseInt(payloads[i]); } catch (NumberFormatException e) { Logger.println("Invalid payload in rtpmap: " + payloads[i]); throw new ParseException("Invalid payload int rtpmap: " + payloads[i], 0); } if (payload != 0 && (payload < 96 || payload > 127)) { /* * Not one we can deal with */ continue; } /* * See if it's a supported payload */ MediaInfo m = null; try { m = getMediaInfo(payload); } catch (ParseException e) { Logger.println("ignoring undefined payload " + payload); continue; } if (m.isTelephoneEventPayload()) { continue; } if (mediaInfo == null || mediaInfo.getSampleRate() < m.getSampleRate()) { mediaInfo = m; } else if (mediaInfo.getSampleRate() == m.getSampleRate()) { if (mediaInfo.getChannels() < m.getChannels()) { mediaInfo = m; } } } } if (mediaInfo == null) { Logger.println("No suitable media payload in sdp data " + sdpData); throw new ParseException("No suitable media payload in sdp data " + sdpData, 0); } sdpInfo = new SdpInfo( remoteHost, remotePort, telephoneEventPayload, supportedMedia, mediaInfo, preferredMediaSpecified); t = "a=transmitPayload:"; if ((start = sdpData.indexOf(t)) >= 0) { s = sdpData.substring(start + t.length()); finish = s.indexOf("\r\n"); if (finish > 0) { int payload; s = s.substring(0, finish); payload = Integer.parseInt(s); try { sdpInfo.setTransmitMediaInfo(getMediaInfo(payload)); Logger.println("Set xmit mediaInfo to " + sdpInfo.getTransmitMediaInfo()); } catch (ParseException e) { } } } int ix; t = "a=transmitMediaInfoOk"; if ((ix = sdpData.indexOf(t)) >= 0) { sdpInfo.setTransmitMediaInfoOk(true); } t = "a=userName:"; if ((ix = sdpData.indexOf(t)) >= 0) { String userName = sdpData.substring(ix + t.length()); finish = userName.indexOf("\n"); if (finish > 0) { sdpInfo.setUserName(userName.substring(0, finish).trim()); } else { /* * This is a workaround for a bug where "\r\n" are missing * from the SDP. * XXX This assumes "userName:" is last in the sdp. */ sdpInfo.setUserName(userName.substring(0).trim()); } } t = "a=callId:"; if ((ix = sdpData.indexOf(t)) >= 0) { String callId = sdpData.substring(ix + t.length()); finish = callId.indexOf("\n"); if (finish > 0) { sdpInfo.setCallId( callId.substring(0, finish).trim()); } } t = "a=conferenceId:"; if ((ix = sdpData.indexOf(t)) >= 0) { String conferenceId = sdpData.substring(ix + t.length()); finish = conferenceId.indexOf("\n"); if (finish > 0) { sdpInfo.setConferenceId( conferenceId.substring(0, finish).trim()); } else { /* * This is a workaround for a bug where "\r\n" are missing * from the SDP. * XXX This assumes "conferenceId:" is last in the sdp. */ sdpInfo.setConferenceId(conferenceId.substring(0).trim()); } } if (sdpData.indexOf("a=distributedBridge") >= 0) { sdpInfo.setDistributedBridge(); } t = "a=rtcpAddress:"; if ((ix = sdpData.indexOf(t)) >= 0) { s = sdpData.substring(ix + t.length()); finish = s.indexOf("\n"); if (finish > 0) { s = s.substring(0, finish).trim(); } else { s = s.substring(0).trim(); } String[] tokens = s.split(":"); if (tokens.length != 2) { throw new ParseException("Invalid rtcp address in sdp " + " sdpData " + sdpData, 0); } try { sdpInfo.setRtcpAddress(new InetSocketAddress( InetAddress.getByName(tokens[0]), Integer.parseInt(tokens[1]))); } catch (UnknownHostException e) { throw new ParseException("Invalid rtcp host address in sdp " + " sdpData " + sdpData, 0); } catch (NumberFormatException e) { throw new ParseException("Invalid rtcp port in sdp " + " sdpData " + sdpData, 0); } } return sdpInfo; } private MediaInfo getMediaInfo(int payload) throws ParseException { for (int i = 0; i < supportedMedia.size(); i++) { MediaInfo mediaInfo = (MediaInfo) supportedMedia.elementAt(i); if (mediaInfo.getPayload() == payload) { return mediaInfo; } } throw new ParseException("Unsupported payload " + payload, 0); } } class RtpmapParser { private MediaInfo mediaInfo; /* * An rtpmap entry looks like this: * * a=rtpmap:<payload> <PCMU | PCM | SPEEX>/<sampleRate>/<channels> * or * a=rtpmap:<payload> telephone-event/8000/1 */ public RtpmapParser(String rtpmap) throws ParseException { byte payload; int encoding; int sampleRate; int channels; byte telephoneEventPayload; int start; int finish; finish = rtpmap.indexOf(" "); if (finish < 0) { Logger.println("Invalid rtpmap: " + rtpmap); throw new ParseException("Invalid rtpmap: " + rtpmap, 0); } try { payload = (byte)Integer.parseInt(rtpmap.substring(9, finish)); } catch (NumberFormatException e) { Logger.println("Invalid payload in rtpmap: " + rtpmap); throw new ParseException("Invalid payload in rtpmap: " + rtpmap, 0); } String s = rtpmap.substring(finish + 1); finish = s.indexOf("telephone-event"); if (finish >= 0) { mediaInfo = new MediaInfo(payload, 0, 8000, 1, true); telephoneEventPayload = payload; return; } finish = s.indexOf("CN/"); if (finish >= 0) { return; // ignore this entry } start = s.indexOf("PCM/"); if (start >= 0) { s = s.substring(start + 4); encoding = RtpPacket.PCM_ENCODING; } else { start = s.indexOf("PCMU/"); if (start >= 0) { s = s.substring(start + 5); encoding = RtpPacket.PCMU_ENCODING; } else { start = s.indexOf("SPEEX/"); if (start < 0) { if (Logger.logLevel >= Logger.LOG_INFO) { Logger.println("Ignoring rtpmap entry: " + payload + " " + s); } return; // ignore this entry } s = s.substring(start + 6); encoding = RtpPacket.SPEEX_ENCODING; } } finish = s.indexOf("/"); boolean channelsPresent = true; String rate; if (finish < 0) { channelsPresent = false; rate = s.substring(start); } else { rate = s.substring(start, finish); // point at sample rate } try { sampleRate = Integer.parseInt(rate); } catch (NumberFormatException e) { Logger.println("Invalid sample rate in rtpmap: " + rtpmap); throw new ParseException("Invalid sample rate in rtpmap: " + rtpmap, 0); } if (channelsPresent) { s = s.substring(finish + 1); // point at channels try { channels = Integer.parseInt(s); } catch (NumberFormatException e) { Logger.println("Invalid channels in rtpmap: " + rtpmap); throw new ParseException("Invalid channels in rtpmap: " + rtpmap, 0); } } else { channels = 1; } mediaInfo = new MediaInfo(payload, encoding, sampleRate, channels, false); } public MediaInfo getMediaInfo() { return mediaInfo; } }