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.jingle.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.Logger; 025 026import javax.media.NoProcessorException; 027import javax.media.format.UnsupportedFormatException; 028import javax.media.rtp.rtcp.SenderReport; 029import javax.media.rtp.rtcp.SourceDescription; 030 031import mil.jfcom.cie.media.session.MediaSession; 032import mil.jfcom.cie.media.session.MediaSessionListener; 033import mil.jfcom.cie.media.session.StreamPlayer; 034import mil.jfcom.cie.media.srtp.packetizer.SpeexFormat; 035 036import org.jivesoftware.smackx.jingle.JingleSession; 037import org.jivesoftware.smackx.jingle.media.JingleMediaSession; 038import org.jivesoftware.smackx.jingle.media.PayloadType; 039import org.jivesoftware.smackx.jingle.nat.TransportCandidate; 040 041/** 042 * This Class implements a complete JingleMediaSession. 043 * It sould be used to transmit and receive audio captured from the Mic. 044 * This Class should be automaticly controlled by JingleSession. 045 * But you could also use in any VOIP application. 046 * For better NAT Traversal support this implementation don't support only receive or only transmit. 047 * To receive you MUST transmit. So the only implemented and functionally methods are startTransmit() and stopTransmit() 048 * 049 * @author Thiago Camargo 050 */ 051 052public class AudioMediaSession extends JingleMediaSession implements MediaSessionListener { 053 054 private static final Logger LOGGER = Logger.getLogger(AudioMediaSession.class.getName()); 055 056 private MediaSession mediaSession; 057 058 /** 059 * Create a Session using Speex Codec 060 * 061 * @param localhost localHost 062 * @param localPort localPort 063 * @param remoteHost remoteHost 064 * @param remotePort remotePort 065 * @param eventHandler eventHandler 066 * @param quality quality 067 * @param secure secure 068 * @param micOn micOn 069 * @return MediaSession 070 * @throws NoProcessorException 071 * @throws UnsupportedFormatException 072 * @throws IOException 073 * @throws GeneralSecurityException 074 */ 075 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 { 076 077 SpeexFormat.setFramesPerPacket(1); 078 /** 079 * The master key. Hardcoded for now. 080 */ 081 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}; 082 083 /** 084 * The master salt. Hardcoded for now. 085 */ 086 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}; 087 088 DatagramSocket[] localPorts = MediaSession.getLocalPorts(InetAddress.getByName(localhost), localPort); 089 MediaSession session = MediaSession.createInstance(remoteHost, remotePort, localPorts, quality, secure, masterKey, masterSalt); 090 session.setListener(eventHandler); 091 092 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)}); 093 return session; 094 } 095 096 097 /** 098 * Creates a org.jivesoftware.jingleaudio.jspeex.AudioMediaSession with defined payload type, remote and local candidates 099 * 100 * @param payloadType Payload of the jmf 101 * @param remote the remote information. The candidate that the jmf will be sent to. 102 * @param local the local information. The candidate that will receive the jmf 103 * @param locator media locator 104 */ 105 public AudioMediaSession(final PayloadType payloadType, final TransportCandidate remote, 106 final TransportCandidate local, String locator, JingleSession jingleSession) { 107 super(payloadType, remote, local, locator == null ? "dsound://" : locator, jingleSession); 108 initialize(); 109 } 110 111 /** 112 * Initialize the Audio Channel to make it able to send and receive audio 113 */ 114 public void initialize() { 115 116 String ip; 117 String localIp; 118 int localPort; 119 int remotePort; 120 121 if (this.getLocal().getSymmetric() != null) { 122 ip = this.getLocal().getIp(); 123 localIp = this.getLocal().getLocalIp(); 124 localPort = getFreePort(); 125 remotePort = this.getLocal().getSymmetric().getPort(); 126 127 LOGGER.fine(this.getLocal().getConnection() + " " + ip + ": " + localPort + "->" + remotePort); 128 129 } 130 else { 131 ip = this.getRemote().getIp(); 132 localIp = this.getLocal().getLocalIp(); 133 localPort = this.getLocal().getPort(); 134 remotePort = this.getRemote().getPort(); 135 } 136 137 try { 138 mediaSession = createSession(localIp, localPort, ip, remotePort, this, 2, false, true); 139 } 140 catch (NoProcessorException e) { 141 e.printStackTrace(); 142 } 143 catch (UnsupportedFormatException e) { 144 e.printStackTrace(); 145 } 146 catch (IOException e) { 147 e.printStackTrace(); 148 } 149 catch (GeneralSecurityException e) { 150 e.printStackTrace(); 151 } 152 } 153 154 /** 155 * Starts transmission and for NAT Traversal reasons start receiving also. 156 */ 157 public void startTrasmit() { 158 try { 159 LOGGER.fine("start"); 160 mediaSession.start(true); 161 this.mediaReceived(""); 162 } 163 catch (IOException e) { 164 e.printStackTrace(); 165 } 166 } 167 168 /** 169 * Set transmit activity. If the active is true, the instance should trasmit. 170 * If it is set to false, the instance should pause transmit. 171 * 172 * @param active active state 173 */ 174 public void setTrasmit(boolean active) { 175 // Do nothing 176 } 177 178 /** 179 * For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf 180 */ 181 public void startReceive() { 182 // Do nothing 183 } 184 185 /** 186 * Stops transmission and for NAT Traversal reasons stop receiving also. 187 */ 188 public void stopTrasmit() { 189 if (mediaSession != null) 190 mediaSession.close(); 191 } 192 193 /** 194 * For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf 195 */ 196 public void stopReceive() { 197 // Do nothing 198 } 199 200 public void newStreamIdentified(StreamPlayer streamPlayer) { 201 } 202 203 public void senderReportReceived(SenderReport report) { 204 } 205 206 public void streamClosed(StreamPlayer stream, boolean timeout) { 207 } 208 209 /** 210 * Obtain a free port we can use. 211 * 212 * @return A free port number. 213 */ 214 protected int getFreePort() { 215 ServerSocket ss; 216 int freePort = 0; 217 218 for (int i = 0; i < 10; i++) { 219 freePort = (int) (10000 + Math.round(Math.random() * 10000)); 220 freePort = freePort % 2 == 0 ? freePort : freePort + 1; 221 try { 222 ss = new ServerSocket(freePort); 223 freePort = ss.getLocalPort(); 224 ss.close(); 225 return freePort; 226 } 227 catch (IOException e) { 228 e.printStackTrace(); 229 } 230 } 231 try { 232 ss = new ServerSocket(0); 233 freePort = ss.getLocalPort(); 234 ss.close(); 235 } 236 catch (IOException e) { 237 e.printStackTrace(); 238 } 239 return freePort; 240 } 241}