/** * $RCSfile: ,v $ * $Revision: $ * $Date: $ * * Copyright (C) 2004-2011 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sipmack.media; import java.io.File; import java.io.IOException; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; import java.util.Vector; import javax.media.format.AudioFormat; import javax.media.format.VideoFormat; import javax.sdp.Connection; import javax.sdp.Media; import javax.sdp.MediaDescription; import javax.sdp.Origin; import javax.sdp.SdpException; import javax.sdp.SdpFactory; import javax.sdp.SdpParseException; import javax.sdp.SessionDescription; import javax.sdp.SessionName; import javax.sdp.TimeDescription; import javax.sdp.Version; import net.java.sip.communicator.impl.media.codec.Constants; import net.java.sipmack.common.Log; import net.java.sipmack.sip.Call; import net.java.sipmack.sip.NetworkAddressManager; import net.java.sipmack.sip.SIPConfig; import net.sf.fmj.media.BonusAudioFormatEncodings; import org.jivesoftware.sparkimpl.plugin.phone.JMFInit; import org.jivesoftware.sparkimpl.settings.local.LocalPreferences; import org.jivesoftware.sparkimpl.settings.local.SettingsManager; /** * JmfMediaManager using JMF based API. * It supports GSM, G711 and G723 codecs. * <i>This API only currently works on windows and Mac.</i> * * @author Thiago Camargo */ public class JmfMediaManager { private List<AudioFormat> audioFormats = new ArrayList<AudioFormat>(); private List<VideoFormat> videoFormats = new ArrayList<VideoFormat>(); protected SdpFactory sdpFactory = SdpFactory.getInstance(); /** * Creates a Media Manager instance */ public JmfMediaManager() { setupAudioFormats(); setupVideoFormats(); } /** * Returns a new jingleMediaSession * * @param audioFormat * @param remote * @param local * @return */ public AudioMediaSession createMediaSession(final AudioFormat audioFormat, final TransportCandidate remote, final TransportCandidate local) { return new AudioMediaSession(audioFormat, remote, local, SettingsManager.getLocalPreferences().getAudioDevice()); } /** * Setup API supported AudioFormats */ private void setupAudioFormats() { audioFormats.add(new AudioFormat(Constants.SPEEX_RTP)); audioFormats.add(new AudioFormat(Constants.ALAW_RTP)); audioFormats.add(new AudioFormat(AudioFormat.ULAW_RTP)); audioFormats.add(new AudioFormat(AudioFormat.GSM_RTP)); audioFormats.add(new AudioFormat(BonusAudioFormatEncodings.ILBC_RTP)); audioFormats.add(new AudioFormat(AudioFormat.G723_RTP)); } /** * Setup API supported VideoFormats */ private void setupVideoFormats() { videoFormats.add(new VideoFormat(VideoFormat.H261_RTP)); videoFormats.add(new VideoFormat(VideoFormat.H263_RTP)); } /** * Return all supported Payloads for this Manager * * @return The Payload List */ public List<AudioFormat> getAudioFormats() { return audioFormats; } /** * Return all supported Payloads for this Manager * * @return The Payload List */ public List<VideoFormat> getVideoFormats() { return videoFormats; } /** * Creates a new VideoMedia Session for a given sdpData * * @param sdpData * @return * @throws MediaException */ public VideoMediaSession createVideoMediaSession(String sdpData, int localPort) throws MediaException { SessionDescription sessionDescription = null; if (sdpData == null) { throw new MediaException("The SDP data was null! Cannot open " + "a stream withour an SDP Description!"); } try { sessionDescription = sdpFactory .createSessionDescription(sdpData); } catch (SdpParseException ex) { throw new MediaException("Incorrect SDP data!", ex); } Vector<?> mediaDescriptions; try { mediaDescriptions = sessionDescription .getMediaDescriptions(true); } catch (SdpException ex) { throw new MediaException( "Failed to extract media descriptions from provided session description!", ex); } Connection sessionConnection = sessionDescription.getConnection(); String sessionRemoteAddress = null; if (sessionConnection != null) { try { sessionRemoteAddress = sessionConnection.getAddress(); } catch (SdpParseException ex) { throw new MediaException( "Failed to extract the connection address parameter" + "from privided session description", ex); } } int mediaPort = -1; boolean atLeastOneTransmitterStarted = false; //ArrayList mediaTypes = new ArrayList(); ArrayList<String> remoteAddresses = new ArrayList<String>(); // A hashtable that indicates what addresses are different media // types // coming from. Hashtable<String, String> remoteTransmisionDetails = new Hashtable<String, String>(); // by default everything is supposed to be coming from the address // specified in the session (global) connection parameter so store // this address for now. if (sessionRemoteAddress != null) { remoteTransmisionDetails.put("audio", sessionRemoteAddress); remoteTransmisionDetails.put("video", sessionRemoteAddress); } ArrayList<Integer> ports = new ArrayList<Integer>(); ArrayList<ArrayList<String>> formatSets = new ArrayList<ArrayList<String>>(); ArrayList<String> contents = new ArrayList<String>(); ArrayList<Integer> localPorts = new ArrayList<Integer>(); for (int i = 0; i < mediaDescriptions.size(); i++) { MediaDescription mediaDescription = (MediaDescription) mediaDescriptions .get(i); Media media = mediaDescription.getMedia(); // Media Type String mediaType = null; try { mediaType = media.getMediaType(); } catch (SdpParseException ex) { continue; } // Find ports try { mediaPort = media.getMediaPort(); } catch (SdpParseException ex) { throw (new MediaException( "Failed to extract port for media type [" + mediaType + "]. Ignoring description!", ex)); } // Find formats Vector<?> sdpFormats = null; try { sdpFormats = media.getMediaFormats(true); } catch (SdpParseException ex) { throw (new MediaException( "Failed to extract media formats for media type [" + mediaType + "]. Ignoring description!", ex)); } Connection mediaConnection = mediaDescription.getConnection(); String mediaRemoteAddress = null; if (mediaConnection == null) { if (sessionConnection == null) { throw new MediaException( "A connection parameter was not present in provided session/media description"); } else { mediaRemoteAddress = sessionRemoteAddress; } } else { try { mediaRemoteAddress = mediaConnection.getAddress(); } catch (SdpParseException ex) { throw new MediaException( "Failed to extract the connection address parameter" + "from privided media description", ex); } } // update the remote address for the current media type in case // it is specific for this media (i.e. differs from the main // connection address) remoteTransmisionDetails.put(mediaType, mediaRemoteAddress); // START TRANSMISSION try { remoteAddresses.add(mediaRemoteAddress); ports.add(new Integer(mediaPort)); contents.add(mediaType); // Selecting local ports for NAT if (mediaType.trim().equals("video")) localPorts.add(Integer.valueOf(22444)); else if (mediaType.trim().equals("audio")) localPorts.add(Integer.valueOf(localPort)); else localPorts.add(Integer.valueOf(mediaPort)); formatSets.add(extractTransmittableJmfFormats(sdpFormats)); } catch (MediaException ex) { Log.error("StartMedia", ex); throw (new MediaException( "Could not start a transmitter for media type [" + mediaType + "]\nIgnoring media [" + mediaType + "]!", ex)); } atLeastOneTransmitterStarted = true; } if (atLeastOneTransmitterStarted) { TransportCandidate.Fixed remote = new TransportCandidate.Fixed(remoteAddresses.get(0).toString(), (Integer) ports.get(0)); TransportCandidate.Fixed local = new TransportCandidate.Fixed(NetworkAddressManager.getLocalHost().getHostAddress(), localPort); // TODO : FORCED TO JPEG_RTP -> deteced format // VideoFormat videoFormat = new VideoFormat((String) (((ArrayList) formatSets.get(0)).get(0))); VideoFormat videoFormat = new VideoFormat(VideoFormat.JPEG_RTP); VideoMediaSession videoMediaSession = new VideoMediaSession(videoFormat, remote, local, SettingsManager.getLocalPreferences().getVideoDevice());; return videoMediaSession; } return null; } /** * Creates a new AudioMedia Session for a given sdpData * * @param sdpData * @return * @throws MediaException */ public AudioMediaSession createAudioMediaSession(String sdpData, int localPort) throws MediaException { SessionDescription sessionDescription = null; if (sdpData == null) { throw new MediaException("The SDP data was null! Cannot open " + "a stream withour an SDP Description!"); } try { sessionDescription = sdpFactory .createSessionDescription(sdpData); } catch (SdpParseException ex) { throw new MediaException("Incorrect SDP data!", ex); } Vector<?> mediaDescriptions; try { mediaDescriptions = sessionDescription .getMediaDescriptions(true); } catch (SdpException ex) { throw new MediaException( "Failed to extract media descriptions from provided session description!", ex); } Connection sessionConnection = sessionDescription.getConnection(); String sessionRemoteAddress = null; if (sessionConnection != null) { try { sessionRemoteAddress = sessionConnection.getAddress(); } catch (SdpParseException ex) { throw new MediaException( "Failed to extract the connection address parameter" + "from privided session description", ex); } } int mediaPort = -1; boolean atLeastOneTransmitterStarted = false; //ArrayList mediaTypes = new ArrayList(); ArrayList<String> remoteAddresses = new ArrayList<String>(); // A hashtable that indicates what addresses are different media // types // coming from. Hashtable<String, String> remoteTransmisionDetails = new Hashtable<String, String>(); // by default everything is supposed to be coming from the address // specified in the session (global) connection parameter so store // this address for now. if (sessionRemoteAddress != null) { remoteTransmisionDetails.put("audio", sessionRemoteAddress); remoteTransmisionDetails.put("video", sessionRemoteAddress); } ArrayList<Integer> ports = new ArrayList<Integer>(); ArrayList<ArrayList<String>> formatSets = new ArrayList<ArrayList<String>>(); ArrayList<String> contents = new ArrayList<String>(); ArrayList<Integer> localPorts = new ArrayList<Integer>(); for (int i = 0; i < mediaDescriptions.size(); i++) { MediaDescription mediaDescription = (MediaDescription) mediaDescriptions .get(i); Media media = mediaDescription.getMedia(); // Media Type String mediaType = null; try { mediaType = media.getMediaType(); } catch (SdpParseException ex) { continue; } // Find ports try { mediaPort = media.getMediaPort(); } catch (SdpParseException ex) { throw (new MediaException( "Failed to extract port for media type [" + mediaType + "]. Ignoring description!", ex)); } // Find formats Vector<?> sdpFormats = null; try { sdpFormats = media.getMediaFormats(true); } catch (SdpParseException ex) { throw (new MediaException( "Failed to extract media formats for media type [" + mediaType + "]. Ignoring description!", ex)); } Connection mediaConnection = mediaDescription.getConnection(); String mediaRemoteAddress = null; if (mediaConnection == null) { if (sessionConnection == null) { throw new MediaException( "A connection parameter was not present in provided session/media description"); } else { mediaRemoteAddress = sessionRemoteAddress; } } else { try { mediaRemoteAddress = mediaConnection.getAddress(); } catch (SdpParseException ex) { throw new MediaException( "Failed to extract the connection address parameter" + "from privided media description", ex); } } // update the remote address for the current media type in case // it is specific for this media (i.e. differs from the main // connection address) remoteTransmisionDetails.put(mediaType, mediaRemoteAddress); // START TRANSMISSION try { remoteAddresses.add(mediaRemoteAddress); ports.add(new Integer(mediaPort)); contents.add(mediaType); // Selecting local ports for NAT if (mediaType.trim().equals("video")) localPorts.add(Integer.valueOf(22445)); else if (mediaType.trim().equals("audio")) localPorts.add(Integer.valueOf(localPort)); else localPorts.add(Integer.valueOf(mediaPort)); formatSets .add(extractTransmittableJmfFormats(sdpFormats)); } catch (MediaException ex) { Log.error("StartMedia", ex); throw (new MediaException( "Could not start a transmitter for media type [" + mediaType + "]\nIgnoring media [" + mediaType + "]!", ex)); } atLeastOneTransmitterStarted = true; } if (atLeastOneTransmitterStarted) { TransportCandidate.Fixed remote = new TransportCandidate.Fixed(remoteAddresses.get(0).toString(), (Integer) ports.get(0)); TransportCandidate.Fixed local = new TransportCandidate.Fixed(NetworkAddressManager.getLocalHost().getHostAddress(), localPort); AudioFormat audioFormat = new AudioFormat(((formatSets.get(0)).get(0))); AudioMediaSession audioMediaSession = new AudioMediaSession(audioFormat, remote, local, SettingsManager.getLocalPreferences().getAudioDevice()); return audioMediaSession; } return null; } /** * Creates a new AudioReceiverChannel Session for a given sdpData * * @param localPort localPort * @return * @throws MediaException */ public AudioReceiverChannel createAudioReceiverChannel(int localPort, String remoteIp, int remotePort) throws MediaException { AudioReceiverChannel audioReceiverChannel = new AudioReceiverChannel(NetworkAddressManager.getLocalHost().getHostAddress(), localPort, remoteIp, remotePort); return audioReceiverChannel; } /** * Extract the supported formats for JMF from a Vector * * @param sdpFormats * @return * @throws MediaException */ protected ArrayList<String> extractTransmittableJmfFormats(Vector<?> sdpFormats) throws MediaException { ArrayList<String> jmfFormats = new ArrayList<String>(); for (int i = 0; i < sdpFormats.size(); i++) { String jmfFormat = AudioFormatUtils.findCorrespondingJmfFormat(sdpFormats .elementAt(i).toString()); if (jmfFormat != null) { jmfFormats.add(jmfFormat); } } if (jmfFormats.size() == 0) { throw new MediaException( "None of the supplied sdp formats for is supported by SIP COMMUNICATOR"); } return jmfFormats; } public SessionDescription generateSdpDescription() throws MediaException { try { SessionDescription sessDescr = sdpFactory .createSessionDescription(); int audioPort, videoPort; audioPort = (int) (5000 * Math.random()) + 5000; if (audioPort % 2 != 0) audioPort++; do { videoPort = (int) (5000 * Math.random()) + 5000; if (videoPort % 2 != 0) videoPort++; } while (audioPort == videoPort); // videoPort = "22497"; // audioPort = "16251"; Version v = sdpFactory.createVersion(0); InetSocketAddress publicVideoAddress = NetworkAddressManager .getPublicAddressFor(videoPort); InetSocketAddress publicAudioAddress = NetworkAddressManager .getPublicAddressFor(audioPort); InetAddress publicIpAddress = publicAudioAddress.getAddress(); String addrType = publicIpAddress instanceof Inet6Address ? "IP6" : "IP4"; // spaces in the user name mess everything up. // bug report - Alessandro Melzi Origin o = sdpFactory.createOrigin(SIPConfig.getUserName() .replace(' ', '_'), 20109217, 2, "IN", addrType, publicIpAddress.getHostAddress()); // "s=-" SessionName s = sdpFactory.createSessionName("<SIPmack>"); // c= Connection c = sdpFactory.createConnection("IN", addrType, publicIpAddress.getHostAddress()); // "t=0 0" TimeDescription t = sdpFactory.createTimeDescription(); Vector<TimeDescription> timeDescs = new Vector<TimeDescription>(); timeDescs.add(t); // --------Audio media description // make sure preferred formats come first String[] aformats = new String[getSelectedFormats().size()]; int i = 0; for (AudioFormat audioFormat : getSelectedFormats()) { aformats[i++] = AudioFormatUtils.findCorrespondingSdpFormat(audioFormat.getEncoding()); } MediaDescription am = sdpFactory.createMediaDescription( "audio", publicAudioAddress.getPort(), 1, "RTP/AVP", aformats); am.setAttribute("sendrecv", null); am.setAttribute("rtmap:101", "telephone-event/" + publicAudioAddress.getPort()); i = 0; String[] vformats = new String[getVideoFormats().size()]; for (VideoFormat videoFormat : getVideoFormats()) { vformats[i++] = AudioFormatUtils.findCorrespondingSdpFormat(videoFormat.getEncoding()); } MediaDescription vam = sdpFactory.createMediaDescription( "video", publicVideoAddress.getPort(), 1, "RTP/AVP", vformats); Vector<MediaDescription> mediaDescs = new Vector<MediaDescription>(); mediaDescs.add(am); mediaDescs.add(vam); sessDescr.setVersion(v); sessDescr.setOrigin(o); sessDescr.setConnection(c); sessDescr.setSessionName(s); sessDescr.setTimeDescriptions(timeDescs); if (mediaDescs.size() > 0) sessDescr.setMediaDescriptions(mediaDescs); return sessDescr; } catch (SdpException exc) { throw new MediaException( "An SDP exception occurred while generating local sdp description", exc); } } /** * Generates the Hold Description for a Call. * * @param setAudio set hold on Audio. * @param setVideo set hold on Video. * @param call the call that you want to hold. * @return SessionDescription of a call. * @throws MediaException */ public SessionDescription generateHoldSdpDescription(boolean setAudio, boolean setVideo, Call call) throws MediaException { try { SessionDescription sessDescr = sdpFactory .createSessionDescription(); Version v = sdpFactory.createVersion(0); InetSocketAddress publicAudioAddress = NetworkAddressManager .getPublicAddressFor(((MediaDescription) (call.getLocalSdpDescription().getMediaDescriptions(true).get(0))).getMedia().getMediaPort()); InetAddress publicIpAddress = publicAudioAddress.getAddress(); String addrType = publicIpAddress instanceof Inet6Address ? "IP6" : "IP4"; Origin o = sdpFactory.createOrigin(SIPConfig.getUserName() .replace(' ', '_'), 20109217, 2, "IN", addrType, publicIpAddress.getHostAddress()); SessionName s = sdpFactory.createSessionName("<SparkPhone>"); Connection c = sdpFactory.createConnection("IN", addrType, publicIpAddress.getHostAddress()); TimeDescription t = sdpFactory.createTimeDescription(); Vector<TimeDescription> timeDescs = new Vector<TimeDescription>(); timeDescs.add(t); String[] formats = new String[getAudioFormats().size()]; int i = 0; for (AudioFormat audioFormat : getAudioFormats()) { formats[i++] = AudioFormatUtils.findCorrespondingSdpFormat(audioFormat.getEncoding()); } MediaDescription am = sdpFactory.createMediaDescription( "audio", publicAudioAddress.getPort(), 1, "RTP/AVP", formats); am.setAttribute(setAudio ? "sendonly" : "sendrecv", null); am.setAttribute("rtmap:101", "telephone-event/" + publicAudioAddress.getPort()); Vector<MediaDescription> mediaDescs = new Vector<MediaDescription>(); mediaDescs.add(am); sessDescr.setVersion(v); sessDescr.setOrigin(o); sessDescr.setConnection(c); sessDescr.setSessionName(s); sessDescr.setTimeDescriptions(timeDescs); if (mediaDescs.size() > 0) sessDescr.setMediaDescriptions(mediaDescs); return sessDescr; } catch (SdpException exc) { throw new MediaException( "An SDP exception occurred while generating local sdp description", exc); } } /** * Runs JMFInit the first time the application is started so that capture * devices are properly detected and initialized by JMF. */ public static void setupJMF() { // .jmf is the place where we store the jmf.properties file used // by JMF. if the directory does not exist or it does not contain // a jmf.properties file. or if the jmf.properties file has 0 length // then this is the first time we're running and should continue to // with JMFInit String homeDir = System.getProperty("user.home"); File jmfDir = new File(homeDir, ".jmf"); String classpath = System.getProperty("java.class.path"); classpath += System.getProperty("path.separator") + jmfDir.getAbsolutePath(); System.setProperty("java.class.path", classpath); if (!jmfDir.exists()) jmfDir.mkdir(); File jmfProperties = new File(jmfDir, "jmf.properties"); if (!jmfProperties.exists()) { try { jmfProperties.createNewFile(); } catch (IOException ex) { System.out.println("Failed to create jmf.properties"); ex.printStackTrace(); } } // if we're running on linux checkout that libjmutil.so is where it // should be and put it there. runLinuxPreInstall(); //if (jmfProperties.length() == 0) { new JMFInit(null, false); //} } private static void runLinuxPreInstall() { // @TODO Implement Linux Pre-Install } /** * reads the codec-order from the user-preferences * * @return */ //TODO REMOVE @SuppressWarnings("unused") private AudioFormat getPreferredFormat(ArrayList<String> formate){ LocalPreferences localPreferences = SettingsManager.getLocalPreferences(); // gets the selected order from preferences String[] codecs = localPreferences.getSelectedCodecs().split("\\^"); for(String codec: codecs) { for(String format : formate) { if(format != null && format.toLowerCase().equals(codec.toLowerCase())) return new AudioFormat(format); } } // this return shouldn't be executed, but if no codec was found, then use the first return new AudioFormat(formate.get(0)); } private List<AudioFormat> getSelectedFormats(){ List<AudioFormat> format = new ArrayList<AudioFormat>(); List<AudioFormat> all = getAudioFormats(); LocalPreferences localPreferences = SettingsManager.getLocalPreferences(); // gets the selected order from preferences String[] codecs = localPreferences.getSelectedCodecs().split("\\^"); for(AudioFormat form : all) { for(String codec: codecs) { if(form.getEncoding().toLowerCase().equals(codec.toLowerCase())){ format.add(form); } } } System.out.println("FORMATE NEU: " + format); return format; } }