1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-12-06 05:01:12 +01:00

Jingle Extension added to Smack Repository

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@6517 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Thiago Camargo 2007-01-04 17:25:30 +00:00 committed by thiago
parent f1972c2571
commit 4b6de6647b
104 changed files with 16411 additions and 0 deletions

View file

@ -0,0 +1,157 @@
package org.jivesoftware.demo;
import org.jivesoftware.jingleaudio.jmf.JmfMediaManager;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.IncomingJingleSession;
import org.jivesoftware.smackx.jingle.JingleManager;
import org.jivesoftware.smackx.jingle.JingleSessionRequest;
import org.jivesoftware.smackx.jingle.OutgoingJingleSession;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener;
import org.jivesoftware.smackx.jingle.nat.BridgedTransportManager;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.RTPBridge;
import org.jivesoftware.smackx.jingle.nat.STUNTransportManager;
import javax.swing.*;
import java.awt.event.ActionEvent;
/**
* $RCSfile$
* $Revision: $
* $Date: 28/12/2006
*
* Copyright 2003-2006 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.
*/
public class Demo extends JFrame {
private JingleTransportManager transportManager = null;
private XMPPConnection xmppConnection = null;
private String server = null;
private String user = null;
private String pass = null;
private JingleManager jm = null;
private IncomingJingleSession incoming = null;
private OutgoingJingleSession outgoing = null;
private JTextField jid = new JTextField(30);
public Demo(String server, String user, String pass) {
this.server = server;
this.user = user;
this.pass = pass;
xmppConnection = new XMPPConnection(server);
try {
xmppConnection.connect();
xmppConnection.login(user, pass);
initialize();
} catch (XMPPException e) {
e.printStackTrace();
}
}
public void initialize() {
if (RTPBridge.serviceAvailable(xmppConnection))
transportManager = new BridgedTransportManager(xmppConnection);
else
transportManager = new STUNTransportManager();
jm = new JingleManager(xmppConnection, transportManager, new JmfMediaManager());
if (transportManager instanceof BridgedTransportManager)
jm.addCreationListener((BridgedTransportManager) transportManager);
jm.addJingleSessionRequestListener(new JingleSessionRequestListener() {
public void sessionRequested(JingleSessionRequest request) {
if (incoming != null)
return;
try {
// Accept the call
incoming = request.accept();
// Start the call
incoming.start();
}
catch (XMPPException e) {
e.printStackTrace();
}
}
});
createGUI();
}
public void createGUI() {
JPanel jPanel = new JPanel();
jPanel.add(jid);
jPanel.add(new JButton(new AbstractAction("Call") {
public void actionPerformed(ActionEvent e) {
if (outgoing != null) return;
try {
outgoing = jm.createOutgoingJingleSession(jid.getText());
} catch (XMPPException e1) {
e1.printStackTrace();
}
}
}));
jPanel.add(new JButton(new AbstractAction("Hangup") {
public void actionPerformed(ActionEvent e) {
if (outgoing != null)
try {
outgoing.terminate();
} catch (XMPPException e1) {
e1.printStackTrace();
} finally {
outgoing = null;
}
if (incoming != null)
try {
incoming.terminate();
} catch (XMPPException e1) {
e1.printStackTrace();
} finally {
incoming = null;
}
}
}));
this.add(jPanel);
}
public static void main(String args[]) {
Demo demo = null;
if (args.length > 2) {
demo = new Demo(args[0], args[1], args[2]);
demo.pack();
demo.setVisible(true);
demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
}

View file

@ -0,0 +1,439 @@
/**
* $RCSfile$
* $Revision: $
* $Date: 08/11/2006
* <p/>
* Copyright 2003-2006 Jive Software.
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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 org.jivesoftware.jingleaudio.jmf;
import javax.media.*;
import javax.media.control.TrackControl;
import javax.media.format.AudioFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushBufferDataSource;
import javax.media.protocol.PushBufferStream;
import javax.media.rtp.RTPManager;
import javax.media.rtp.SendStream;
import javax.media.rtp.SessionAddress;
import javax.media.rtp.rtcp.SourceDescription;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
/**
* An Easy to use Audio Channel implemented using JMF.
* It sends and receives jmf for and from desired IPs and ports.
* Also has a rport Symetric behavior for better NAT Traversal.
* It send data from a defined port and receive data in the same port, making NAT binds easier.
* <p/>
* Send from portA to portB and receive from portB in portA.
* <p/>
* Sending
* portA ---> portB
* <p/>
* Receiving
* portB ---> portA
* <p/>
* <i>Transmit and Receive are interdependents. To receive you MUST trasmit. </i>
*/
public class AudioChannel {
private MediaLocator locator;
private String localIpAddress;
private String ipAddress;
private int localPort;
private int portBase;
private Format format;
private Processor processor = null;
private RTPManager rtpMgrs[];
private DataSource dataOutput = null;
private AudioReceiver audioReceiver;
private List<SendStream> sendStreams = new ArrayList<SendStream>();
private boolean started = false;
/**
* Creates an Audio Channel for a desired jmf locator. For instance: new MediaLocator("dsound://")
*
* @param locator
* @param ipAddress
* @param localPort
* @param remotePort
* @param format
*/
public AudioChannel(MediaLocator locator,
String localIpAddress,
String ipAddress,
int localPort,
int remotePort,
Format format) {
this.locator = locator;
this.localIpAddress = localIpAddress;
this.ipAddress = ipAddress;
this.localPort = localPort;
this.portBase = remotePort;
this.format = format;
}
/**
* Starts the transmission. Returns null if transmission started ok.
* Otherwise it returns a string with the reason why the setup failed.
* Starts receive also.
*/
public synchronized String start() {
if (started) return null;
started = true;
String result;
// Create a processor for the specified jmf locator
result = createProcessor();
if (result != null) {
started = false;
return result;
}
// Create an RTP session to transmit the output of the
// processor to the specified IP address and port no.
result = createTransmitter();
if (result != null) {
processor.close();
processor = null;
started = false;
return result;
}
// Start the transmission
processor.start();
return null;
}
/**
* Stops the transmission if already started.
* Stops the receiver also.
*/
public void stop() {
if (!started) return;
synchronized (this) {
try {
started = false;
if (processor != null) {
processor.stop();
processor = null;
for (int i = 0; i < rtpMgrs.length; i++) {
rtpMgrs[i].removeReceiveStreamListener(audioReceiver);
rtpMgrs[i].removeSessionListener(audioReceiver);
rtpMgrs[i].removeTargets("Session ended.");
rtpMgrs[i].dispose();
}
sendStreams.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private String createProcessor() {
if (locator == null)
return "Locator is null";
DataSource ds;
try {
ds = javax.media.Manager.createDataSource(locator);
} catch (Exception e) {
e.printStackTrace();
return "Couldn't create DataSource";
}
// Try to create a processor to handle the input jmf locator
try {
processor = javax.media.Manager.createProcessor(ds);
} catch (NoProcessorException npe) {
npe.printStackTrace();
return "Couldn't create processor";
} catch (IOException ioe) {
ioe.printStackTrace();
return "IOException creating processor";
}
// Wait for it to configure
boolean result = waitForState(processor, Processor.Configured);
if (result == false)
return "Couldn't configure processor";
// Get the tracks from the processor
TrackControl[] tracks = processor.getTrackControls();
// Do we have atleast one track?
if (tracks == null || tracks.length < 1)
return "Couldn't find tracks in processor";
// Set the output content descriptor to RAW_RTP
// This will limit the supported formats reported from
// Track.getSupportedFormats to only valid RTP formats.
ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
processor.setContentDescriptor(cd);
Format supported[];
Format chosen = null;
boolean atLeastOneTrack = false;
// Program the tracks.
for (int i = 0; i < tracks.length; i++) {
if (tracks[i].isEnabled()) {
supported = tracks[i].getSupportedFormats();
if (supported.length > 0) {
for (Format format : supported) {
if (format instanceof AudioFormat) {
if (this.format.matches(format))
chosen = format;
}
}
if (chosen != null) {
tracks[i].setFormat(chosen);
System.err.println("Track " + i + " is set to transmit as:");
System.err.println(" " + chosen);
atLeastOneTrack = true;
} else
tracks[i].setEnabled(false);
} else
tracks[i].setEnabled(false);
}
}
if (!atLeastOneTrack)
return "Couldn't set any of the tracks to a valid RTP format";
result = waitForState(processor, Controller.Realized);
if (result == false)
return "Couldn't realize processor";
// Get the output data source of the processor
dataOutput = processor.getDataOutput();
return null;
}
/**
* Use the RTPManager API to create sessions for each jmf
* track of the processor.
*/
private String createTransmitter() {
// Cheated. Should have checked the type.
PushBufferDataSource pbds = (PushBufferDataSource) dataOutput;
PushBufferStream pbss[] = pbds.getStreams();
rtpMgrs = new RTPManager[pbss.length];
SessionAddress localAddr, destAddr;
InetAddress ipAddr;
SendStream sendStream;
audioReceiver = new AudioReceiver(this);
int port;
SourceDescription srcDesList[];
for (int i = 0; i < pbss.length; i++) {
try {
rtpMgrs[i] = RTPManager.newInstance();
port = portBase + 2 * i;
ipAddr = InetAddress.getByName(ipAddress);
localAddr = new SessionAddress(InetAddress.getByName(this.localIpAddress),
localPort);
destAddr = new SessionAddress(ipAddr, port);
rtpMgrs[i].addReceiveStreamListener(audioReceiver);
rtpMgrs[i].addSessionListener(audioReceiver);
rtpMgrs[i].initialize(localAddr);
rtpMgrs[i].addTarget(destAddr);
System.err.println("Created RTP session at " + localPort + " to: " + ipAddress + " " + port);
sendStream = rtpMgrs[i].createSendStream(dataOutput, i);
sendStreams.add(sendStream);
sendStream.start();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}
return null;
}
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
*
* @param active
*/
public void setTrasmit(boolean active) {
for (SendStream sendStream : sendStreams) {
try {
if (active) {
sendStream.start();
System.out.println("START");
} else {
sendStream.stop();
System.out.println("STOP");
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* *************************************************************
* Convenience methods to handle processor's state changes.
* **************************************************************
*/
private Integer stateLock = new Integer(0);
private boolean failed = false;
Integer getStateLock() {
return stateLock;
}
void setFailed() {
failed = true;
}
private synchronized boolean waitForState(Processor p, int state) {
p.addControllerListener(new StateListener());
failed = false;
// Call the required method on the processor
if (state == Processor.Configured) {
p.configure();
} else if (state == Processor.Realized) {
p.realize();
}
// Wait until we get an event that confirms the
// success of the method, or a failure event.
// See StateListener inner class
while (p.getState() < state && !failed) {
synchronized (getStateLock()) {
try {
getStateLock().wait();
} catch (InterruptedException ie) {
return false;
}
}
}
if (failed)
return false;
else
return true;
}
/**
* *************************************************************
* Inner Classes
* **************************************************************
*/
class StateListener implements ControllerListener {
public void controllerUpdate(ControllerEvent ce) {
// If there was an error during configure or
// realize, the processor will be closed
if (ce instanceof ControllerClosedEvent)
setFailed();
// All controller events, send a notification
// to the waiting thread in waitForState method.
if (ce instanceof ControllerEvent) {
synchronized (getStateLock()) {
getStateLock().notifyAll();
}
}
}
}
public static void main(String args[]) {
InetAddress localhost;
try {
localhost = InetAddress.getLocalHost();
AudioChannel audioChannel0 = new AudioChannel(new MediaLocator("javasound://8000"), localhost.getHostAddress(), localhost.getHostAddress(), 7002, 7020, new AudioFormat(AudioFormat.GSM_RTP));
AudioChannel audioChannel1 = new AudioChannel(new MediaLocator("javasound://8000"), localhost.getHostAddress(), localhost.getHostAddress(), 7020, 7002, new AudioFormat(AudioFormat.GSM_RTP));
audioChannel0.start();
audioChannel1.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
audioChannel0.setTrasmit(false);
audioChannel1.setTrasmit(false);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
audioChannel0.setTrasmit(true);
audioChannel1.setTrasmit(true);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
audioChannel0.stop();
audioChannel1.stop();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,51 @@
/**
* $RCSfile$
* $Revision: $
* $Date: 08/11/2006
* <p/>
* Copyright 2003-2006 Jive Software.
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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 org.jivesoftware.jingleaudio.jmf;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import javax.media.format.AudioFormat;
/**
* Audio Format Ttils.
*/
public class AudioFormatUtils {
/**
* Return a JMF AudioFormat for a given Jingle Payload type.
* Return null if the payload is not supported by this jmf API.
*
* @param payloadtype
* @return
*/
public static AudioFormat getAudioFormat(PayloadType payloadtype) {
switch (payloadtype.getId()) {
case 3:
return new AudioFormat(AudioFormat.GSM_RTP);
case 4:
return new AudioFormat(AudioFormat.G723_RTP);
default:
return null;
}
}
}

View file

@ -0,0 +1,155 @@
package org.jivesoftware.jingleaudio.jmf; /**
* $RCSfile$
* $Revision: $
* $Date: 08/11/2006
* <p/>
* Copyright 2003-2006 Jive Software.
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.
*/
import org.jivesoftware.jingleaudio.jmf.AudioChannel;
import org.jivesoftware.jingleaudio.jmf.AudioFormatUtils;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import javax.media.MediaLocator;
import javax.media.format.AudioFormat;
import java.io.IOException;
import java.net.ServerSocket;
/**
* This Class implements a complete JingleMediaSession.
* It sould be used to transmit and receive audio captured from the Mic.
* This Class should be automaticly controlled by JingleSession.
* But you could also use in any VOIP application.
* For better NAT Traversal support this implementation don´t support only receive or only transmit.
* To receive you MUST transmit. So the only implemented and functionally methods are startTransmit() and stopTransmit()
*/
public class AudioMediaSession extends JingleMediaSession {
private AudioFormat format;
private AudioChannel audioChannel;
/**
* Creates a org.jivesoftware.jingleaudio.jmf.AudioMediaSession with defined payload type, remote and local candidates
*
* @param payloadType Payload of the jmf
* @param remote The remote information. The candidate that the jmf will be sent to.
* @param local The local information. The candidate that will receive the jmf
*/
public AudioMediaSession(final PayloadType payloadType, final TransportCandidate remote,
final TransportCandidate local) {
super(payloadType, remote, local);
}
/**
* Initialize the Audio Channel to make it able to send and receive audio
*/
public void initialize() {
String ip;
String localIp;
int localPort;
int remotePort;
if (this.getLocal().getSymmetric() != null) {
ip = this.getLocal().getIp();
localIp = this.getLocal().getLocalIp();
localPort = getFreePort();
remotePort = this.getLocal().getSymmetric().getPort();
System.out.println(this.getLocal().getConnection() + " " + ip + ": " + localPort + "->" + remotePort);
} else {
ip = this.getRemote().getIp();
localIp = this.getLocal().getLocalIp();
localPort = this.getLocal().getPort();
remotePort = this.getRemote().getPort();
}
audioChannel = new AudioChannel(new MediaLocator("dsound://"), localIp, ip, localPort, remotePort, AudioFormatUtils.getAudioFormat(this.getPayloadType()));
}
/**
* Starts transmission and for NAT Traversal reasons start receiving also.
*/
public void startTrasmit() {
audioChannel.start();
}
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
*
* @param active
*/
public void setTrasmit(boolean active) {
audioChannel.setTrasmit(active);
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void startReceive() {
// Do nothing
}
/**
* Stops transmission and for NAT Traversal reasons stop receiving also.
*/
public void stopTrasmit() {
audioChannel.stop();
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void stopReceive() {
// Do nothing
}
/**
* Obtain a free port we can use.
*
* @return A free port number.
*/
protected int getFreePort() {
ServerSocket ss;
int freePort = 0;
for (int i = 0; i < 10; i++) {
freePort = (int) (10000 + Math.round(Math.random() * 10000));
freePort = freePort % 2 == 0 ? freePort : freePort + 1;
try {
ss = new ServerSocket(freePort);
freePort = ss.getLocalPort();
ss.close();
return freePort;
}
catch (IOException e) {
e.printStackTrace();
}
}
try {
ss = new ServerSocket(0);
freePort = ss.getLocalPort();
ss.close();
}
catch (IOException e) {
e.printStackTrace();
}
return freePort;
}
}

View file

@ -0,0 +1,143 @@
package org.jivesoftware.jingleaudio.jmf; /**
* $RCSfile$
* $Revision: $
* $Date: 08/11/2006
* <p/>
* Copyright 2003-2006 Jive Software.
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.
*/
import javax.media.*;
import javax.media.protocol.DataSource;
import javax.media.rtp.*;
import javax.media.rtp.event.*;
/**
* This class implements receive methods and listeners to be used in AudioChannel
*/
public class AudioReceiver implements ReceiveStreamListener, SessionListener,
ControllerListener {
boolean dataReceived = false;
Object dataSync;
public AudioReceiver(Object dataSync) {
this.dataSync = dataSync;
}
/**
* JingleSessionListener.
*/
public synchronized void update(SessionEvent evt) {
if (evt instanceof NewParticipantEvent) {
Participant p = ((NewParticipantEvent) evt).getParticipant();
System.err.println(" - A new participant had just joined: " + p.getCNAME());
}
}
/**
* ReceiveStreamListener
*/
public synchronized void update(ReceiveStreamEvent evt) {
RTPManager mgr = (RTPManager) evt.getSource();
Participant participant = evt.getParticipant(); // could be null.
ReceiveStream stream = evt.getReceiveStream(); // could be null.
if (evt instanceof RemotePayloadChangeEvent) {
System.err.println(" - Received an RTP PayloadChangeEvent.");
System.err.println("Sorry, cannot handle payload change.");
// System.exit(0);
} else if (evt instanceof NewReceiveStreamEvent) {
try {
stream = ((NewReceiveStreamEvent) evt).getReceiveStream();
DataSource ds = stream.getDataSource();
// Find out the formats.
RTPControl ctl = (RTPControl) ds.getControl("javax.jmf.rtp.RTPControl");
if (ctl != null) {
System.err.println(" - Recevied new RTP stream: " + ctl.getFormat());
} else
System.err.println(" - Recevied new RTP stream");
if (participant == null)
System.err.println(" The sender of this stream had yet to be identified.");
else {
System.err.println(" The stream comes from: " + participant.getCNAME());
}
// create a player by passing datasource to the Media Manager
Player p = javax.media.Manager.createPlayer(ds);
if (p == null)
return;
p.addControllerListener(this);
p.realize();
// Notify intialize() that a new stream had arrived.
synchronized (dataSync) {
dataReceived = true;
dataSync.notifyAll();
}
} catch (Exception e) {
System.err.println("NewReceiveStreamEvent exception " + e.getMessage());
return;
}
} else if (evt instanceof StreamMappedEvent) {
if (stream != null && stream.getDataSource() != null) {
DataSource ds = stream.getDataSource();
// Find out the formats.
RTPControl ctl = (RTPControl) ds.getControl("javax.jmf.rtp.RTPControl");
System.err.println(" - The previously unidentified stream ");
if (ctl != null)
System.err.println(" " + ctl.getFormat());
System.err.println(" had now been identified as sent by: " + participant.getCNAME());
}
} else if (evt instanceof ByeEvent) {
System.err.println(" - Got \"bye\" from: " + participant.getCNAME());
}
}
/**
* ControllerListener for the Players.
*/
public synchronized void controllerUpdate(ControllerEvent ce) {
Player p = (Player) ce.getSourceController();
if (p == null)
return;
// Get this when the internal players are realized.
if (ce instanceof RealizeCompleteEvent) {
p.start();
}
if (ce instanceof ControllerErrorEvent) {
p.removeControllerListener(this);
System.err.println("Receiver internal error: " + ce);
}
}
}

View file

@ -0,0 +1,119 @@
package org.jivesoftware.jingleaudio.jmf;
/**
* $RCSfile$
* $Revision: $
* $Date: 08/11/2006
* <p/>
* Copyright 2003-2006 Jive Software.
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.
*/
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import java.io.File;
import java.io.IOException;
/**
* Implements a jingleMediaManager using JMF based API.
* It supports GSM and G723 codecs.
* <i>This API only currently works on windows.</i>
*/
public class JmfMediaManager extends JingleMediaManager {
/**
* Creates a Media Manager instance
*/
public JmfMediaManager() {
setupPayloads();
setupJMF();
}
/**
* Returns a new jingleMediaSession
*
* @param payloadType
* @param remote
* @param local
* @return
*/
public JingleMediaSession createMediaSession(final PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local) {
return new AudioMediaSession(payloadType, remote, local);
}
/**
* Setup API supported Payloads
*/
private void setupPayloads() {
this.addPayloadType(new PayloadType.Audio(3, "gsm"));
this.addPayloadType(new PayloadType.Audio(4, "g723"));
}
/**
* Runs JMFInit the first time the application is started so that capture
* devices are properly detected and initialized by JMF.
*/
public static void setupJMF() {
try {
// .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) {
//JMFInit init = new JMFInit(null);
//init.setVisible(false);
}
}
finally {
}
}
private static void runLinuxPreInstall() {
// @TODO Implement Linux Pre-Install
}
}

View file

@ -0,0 +1,209 @@
package org.jivesoftware.jingleaudio.jspeex;
import mil.jfcom.cie.media.session.MediaSession;
import mil.jfcom.cie.media.session.MediaSessionListener;
import mil.jfcom.cie.media.session.StreamPlayer;
import mil.jfcom.cie.media.srtp.packetizer.SpeexFormat;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import javax.media.NoProcessorException;
import javax.media.format.UnsupportedFormatException;
import javax.media.rtp.rtcp.SenderReport;
import javax.media.rtp.rtcp.SourceDescription;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.security.GeneralSecurityException;
/**
* $RCSfile$
* $Revision: $
* $Date: 25/12/2006
*
* Copyright 2003-2006 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.
*/
public class AudioMediaSession extends JingleMediaSession implements MediaSessionListener {
private MediaSession mediaSession;
/**
* Create session for test program.
*
* @param micOn microphone turned on
*/
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 {
SpeexFormat.setFramesPerPacket(1);
/**
* The master key. Hardcoded for now.
*/
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};
/**
* The master salt. Hardcoded for now.
*/
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};
DatagramSocket[] localPorts = MediaSession.getLocalPorts(InetAddress.getByName(localhost), localPort);
MediaSession session = MediaSession.createInstance(remoteHost, remotePort, localPorts, quality, secure, masterKey, masterSalt);
session.setListener(eventHandler);
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)});
return session;
}
/**
* Creates a org.jivesoftware.jingleaudio.jmf.AudioMediaSession with defined payload type, remote and local candidates
*
* @param payloadType Payload of the jmf
* @param remote The remote information. The candidate that the jmf will be sent to.
* @param local The local information. The candidate that will receive the jmf
*/
public AudioMediaSession(final PayloadType payloadType, final TransportCandidate remote,
final TransportCandidate local) {
super(payloadType, remote, local);
}
/**
* Initialize the Audio Channel to make it able to send and receive audio
*/
public void initialize() {
String ip;
String localIp;
int localPort;
int remotePort;
if (this.getLocal().getSymmetric() != null) {
ip = this.getLocal().getIp();
localIp = this.getLocal().getLocalIp();
localPort = getFreePort();
remotePort = this.getLocal().getSymmetric().getPort();
System.out.println(this.getLocal().getConnection() + " " + ip + ": " + localPort + "->" + remotePort);
} else {
ip = this.getRemote().getIp();
localIp = this.getLocal().getLocalIp();
localPort = this.getLocal().getPort();
remotePort = this.getRemote().getPort();
}
try {
mediaSession = createSession(localIp, localPort, ip, remotePort, this, 2, false, true);
} catch (NoProcessorException e) {
e.printStackTrace();
} catch (UnsupportedFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
}
/**
* Starts transmission and for NAT Traversal reasons start receiving also.
*/
public void startTrasmit() {
try {
System.out.println("start");
mediaSession.start(true);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
*
* @param active
*/
public void setTrasmit(boolean active) {
// Do nothing
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void startReceive() {
// Do nothing
}
/**
* Stops transmission and for NAT Traversal reasons stop receiving also.
*/
public void stopTrasmit() {
// Do nothing
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void stopReceive() {
// Do nothing
}
public void newStreamIdentified(StreamPlayer streamPlayer) {
//To change body of implemented methods use File | Settings | File Templates.
}
public void senderReportReceived(SenderReport report) {
//To change body of implemented methods use File | Settings | File Templates.
}
public void streamClosed(StreamPlayer stream, boolean timeout) {
//To change body of implemented methods use File | Settings | File Templates.
}
/**
* Obtain a free port we can use.
*
* @return A free port number.
*/
protected int getFreePort() {
ServerSocket ss;
int freePort = 0;
for (int i = 0; i < 10; i++) {
freePort = (int) (10000 + Math.round(Math.random() * 10000));
freePort = freePort % 2 == 0 ? freePort : freePort + 1;
try {
ss = new ServerSocket(freePort);
freePort = ss.getLocalPort();
ss.close();
return freePort;
}
catch (IOException e) {
e.printStackTrace();
}
}
try {
ss = new ServerSocket(0);
freePort = ss.getLocalPort();
ss.close();
}
catch (IOException e) {
e.printStackTrace();
}
return freePort;
}
}

View file

@ -0,0 +1,101 @@
package org.jivesoftware.jingleaudio.jspeex;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import java.io.File;
import java.io.IOException;
/**
* $RCSfile$
* $Revision: $
* $Date: 25/12/2006
*
* Copyright 2003-2006 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.
*/
public class SpeexMediaManager extends JingleMediaManager {
public SpeexMediaManager() {
setupPayloads();
setupJMF();
}
public JingleMediaSession createMediaSession(PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local) {
return new AudioMediaSession(payloadType, remote, local);
}
/**
* Setup API supported Payloads
*/
private void setupPayloads() {
this.addPayloadType(new PayloadType.Audio(15, "speex"));
}
/**
* Runs JMFInit the first time the application is started so that capture
* devices are properly detected and initialized by JMF.
*/
public static void setupJMF() {
try {
// .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) {
//JMFInit init = new JMFInit(null);
//init.setVisible(false);
}
}
finally {
}
}
private static void runLinuxPreInstall() {
// @TODO Implement Linux Pre-Install
}
}