001/** 002 * 003 * Copyright 2003-2006 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.jingleold.mediaimpl.jspeex; 018 019import java.io.IOException; 020import java.net.DatagramSocket; 021import java.net.InetAddress; 022import java.net.ServerSocket; 023import java.security.GeneralSecurityException; 024import java.util.logging.Level; 025import java.util.logging.Logger; 026 027import javax.media.NoProcessorException; 028import javax.media.format.UnsupportedFormatException; 029import javax.media.rtp.rtcp.SenderReport; 030import javax.media.rtp.rtcp.SourceDescription; 031 032import org.jivesoftware.smackx.jingleold.JingleSession; 033import org.jivesoftware.smackx.jingleold.media.JingleMediaSession; 034import org.jivesoftware.smackx.jingleold.media.PayloadType; 035import org.jivesoftware.smackx.jingleold.nat.TransportCandidate; 036 037import mil.jfcom.cie.media.session.MediaSession; 038import mil.jfcom.cie.media.session.MediaSessionListener; 039import mil.jfcom.cie.media.session.StreamPlayer; 040import mil.jfcom.cie.media.srtp.packetizer.SpeexFormat; 041 042/** 043 * This Class implements a complete JingleMediaSession. 044 * It should be used to transmit and receive audio captured from the Mic. 045 * This Class should be automatically controlled by JingleSession. 046 * But you could also use in any VOIP application. 047 * For better NAT Traversal support this implementation don't support only receive or only transmit. 048 * To receive you MUST transmit. So the only implemented and functionally methods are startTransmit() and stopTransmit() 049 * 050 * @author Thiago Camargo 051 */ 052 053public class AudioMediaSession extends JingleMediaSession implements MediaSessionListener { 054 055 private static final Logger LOGGER = Logger.getLogger(AudioMediaSession.class.getName()); 056 057 private MediaSession mediaSession; 058 059 /** 060 * Create a Session using Speex Codec. 061 * 062 * @param localhost localHost 063 * @param localPort localPort 064 * @param remoteHost remoteHost 065 * @param remotePort remotePort 066 * @param eventHandler eventHandler 067 * @param quality quality 068 * @param secure secure 069 * @param micOn micOn 070 * @return MediaSession TODO javadoc me please 071 * @throws NoProcessorException if there is no media processor. 072 * @throws UnsupportedFormatException if the format is not supported. 073 * @throws IOException if an I/O error occurred. 074 * @throws GeneralSecurityException if there was a general security exception. 075 */ 076 public static MediaSession createSession(String localhost, int localPort, String remoteHost, int remotePort, MediaSessionListener eventHandler, int quality, boolean secure, boolean micOn) throws NoProcessorException, UnsupportedFormatException, IOException, GeneralSecurityException { 077 078 SpeexFormat.setFramesPerPacket(1); 079 /* 080 * The master key. Hardcoded for now. 081 */ 082 byte[] masterKey = new byte[] {(byte) 0xE1, (byte) 0xF9, 0x7A, 0x0D, 0x3E, 0x01, (byte) 0x8B, (byte) 0xE0, (byte) 0xD6, 0x4F, (byte) 0xA3, 0x2C, 0x06, (byte) 0xDE, 0x41, 0x39}; 083 084 /* 085 * The master salt. Hardcoded for now. 086 */ 087 byte[] masterSalt = new byte[] {0x0E, (byte) 0xC6, 0x75, (byte) 0xAD, 0x49, (byte) 0x8A, (byte) 0xFE, (byte) 0xEB, (byte) 0xB6, (byte) 0x96, 0x0B, 0x3A, (byte) 0xAB, (byte) 0xE6}; 088 089 DatagramSocket[] localPorts = MediaSession.getLocalPorts(InetAddress.getByName(localhost), localPort); 090 MediaSession session = MediaSession.createInstance(remoteHost, remotePort, localPorts, quality, secure, masterKey, masterSalt); 091 session.setListener(eventHandler); 092 093 session.setSourceDescription(new SourceDescription[] {new SourceDescription(SourceDescription.SOURCE_DESC_NAME, "Superman", 1, false), new SourceDescription(SourceDescription.SOURCE_DESC_EMAIL, "cdcie.tester@je.jfcom.mil", 1, false), new SourceDescription(SourceDescription.SOURCE_DESC_LOC, InetAddress.getByName(localhost) + " Port " + session.getLocalDataPort(), 1, false), new SourceDescription(SourceDescription.SOURCE_DESC_TOOL, "JFCOM CDCIE Audio Chat", 1, false)}); 094 return session; 095 } 096 097 098 /** 099 * Creates a org.jivesoftware.jingleaudio.jspeex.AudioMediaSession with defined payload type, remote and local candidates. 100 * 101 * @param payloadType Payload of the jmf 102 * @param remote the remote information. The candidate that the jmf will be sent to. 103 * @param local the local information. The candidate that will receive the jmf 104 * @param locator media locator 105 * @param jingleSession the jingle session. 106 */ 107 @SuppressWarnings("this-escape") 108 public AudioMediaSession(final PayloadType payloadType, final TransportCandidate remote, 109 final TransportCandidate local, String locator, JingleSession jingleSession) { 110 super(payloadType, remote, local, locator == null ? "dsound://" : locator, jingleSession); 111 initialize(); 112 } 113 114 /** 115 * Initialize the Audio Channel to make it able to send and receive audio. 116 */ 117 @Override 118 public void initialize() { 119 120 String ip; 121 String localIp; 122 int localPort; 123 int remotePort; 124 125 if (this.getLocal().getSymmetric() != null) { 126 ip = this.getLocal().getIp(); 127 localIp = this.getLocal().getLocalIp(); 128 localPort = getFreePort(); 129 remotePort = this.getLocal().getSymmetric().getPort(); 130 131 LOGGER.fine(this.getLocal().getConnection() + " " + ip + ": " + localPort + "->" + remotePort); 132 133 } 134 else { 135 ip = this.getRemote().getIp(); 136 localIp = this.getLocal().getLocalIp(); 137 localPort = this.getLocal().getPort(); 138 remotePort = this.getRemote().getPort(); 139 } 140 141 try { 142 mediaSession = createSession(localIp, localPort, ip, remotePort, this, 2, false, true); 143 } 144 catch (NoProcessorException e) { 145 LOGGER.log(Level.WARNING, "exception", e); 146 } 147 catch (UnsupportedFormatException e) { 148 LOGGER.log(Level.WARNING, "exception", e); 149 } 150 catch (IOException e) { 151 LOGGER.log(Level.WARNING, "exception", e); 152 } 153 catch (GeneralSecurityException e) { 154 LOGGER.log(Level.WARNING, "exception", e); 155 } 156 } 157 158 /** 159 * Starts transmission and for NAT Traversal reasons start receiving also. 160 * 161 * @deprecated use {@link #startTransmit()} instead. 162 */ 163 @Deprecated 164 public void startTrasmit() { 165 startTransmit(); 166 } 167 168 /** 169 * Starts transmission and for NAT Traversal reasons start receiving also. 170 */ 171 @Override 172 public void startTransmit() { 173 try { 174 LOGGER.fine("start"); 175 mediaSession.start(true); 176 this.mediaReceived(""); 177 } 178 catch (IOException e) { 179 LOGGER.log(Level.WARNING, "exception", e); 180 } 181 } 182 183 /** 184 * Set transmit activity. If the active is true, the instance should transmit. 185 * If it is set to false, the instance should pause transmit. 186 * 187 * @param active active state 188 * @deprecated use {@link #setTransmit(boolean)} instead. 189 */ 190 @Deprecated 191 public void setTrasmit(boolean active) { 192 setTransmit(active); 193 } 194 195 /** 196 * Set transmit activity. If the active is true, the instance should transmit. 197 * If it is set to false, the instance should pause transmit. 198 * 199 * @param active active state 200 */ 201 @Override 202 public void setTransmit(boolean active) { 203 // Do nothing 204 } 205 206 /** 207 * For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf 208 */ 209 @Override 210 public void startReceive() { 211 // Do nothing 212 } 213 214 /** 215 * Stops transmission and for NAT Traversal reasons stop receiving also. 216 * 217 * @deprecated use {@link #stopTransmit()} instead. 218 */ 219 @Deprecated 220 public void stopTrasmit() { 221 stopTransmit(); 222 } 223 224 /** 225 * Stops transmission and for NAT Traversal reasons stop receiving also. 226 */ 227 @Override 228 public void stopTransmit() { 229 if (mediaSession != null) 230 mediaSession.close(); 231 } 232 233 /** 234 * For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf 235 */ 236 @Override 237 public void stopReceive() { 238 // Do nothing 239 } 240 241 @Override 242 public void newStreamIdentified(StreamPlayer streamPlayer) { 243 } 244 245 @Override 246 public void senderReportReceived(SenderReport report) { 247 } 248 249 @Override 250 public void streamClosed(StreamPlayer stream, boolean timeout) { 251 } 252 253 /** 254 * Obtain a free port we can use. 255 * 256 * @return A free port number. 257 */ 258 protected int getFreePort() { 259 ServerSocket ss; 260 int freePort = 0; 261 262 for (int i = 0; i < 10; i++) { 263 freePort = (int) (10000 + Math.round(Math.random() * 10000)); 264 freePort = freePort % 2 == 0 ? freePort : freePort + 1; 265 try { 266 ss = new ServerSocket(freePort); 267 freePort = ss.getLocalPort(); 268 ss.close(); 269 return freePort; 270 } 271 catch (IOException e) { 272 LOGGER.log(Level.WARNING, "exception", e); 273 } 274 } 275 try { 276 ss = new ServerSocket(0); 277 freePort = ss.getLocalPort(); 278 ss.close(); 279 } 280 catch (IOException e) { 281 LOGGER.log(Level.WARNING, "exception", e); 282 } 283 return freePort; 284 } 285}