From 9feb6ece424f0d6f167ade03d75f2f544f971b30 Mon Sep 17 00:00:00 2001 From: Thiago Camargo Date: Fri, 9 Mar 2007 17:43:28 +0000 Subject: [PATCH] Jingle Extension and Media Merged git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@7425 b35dd754-fafc-0310-a699-88a17e54d16e --- .../smackx/jingle/mediaimpl/JMFInit.java | 275 +++++++++++ .../smackx/jingle/mediaimpl/demo/Demo.java | 156 ++++++ .../jingle/mediaimpl/jmf/AudioChannel.java | 454 ++++++++++++++++++ .../mediaimpl/jmf/AudioFormatUtils.java | 55 +++ .../mediaimpl/jmf/AudioMediaSession.java | 174 +++++++ .../jingle/mediaimpl/jmf/AudioReceiver.java | 144 ++++++ .../jingle/mediaimpl/jmf/JmfMediaManager.java | 127 +++++ .../mediaimpl/jspeex/AudioMediaSession.java | 237 +++++++++ .../mediaimpl/jspeex/SpeexMediaManager.java | 114 +++++ .../mediaimpl/multi/MultiMediaManager.java | 84 ++++ .../smackx/jingle/JingleManagerTest.java | 2 +- 11 files changed, 1821 insertions(+), 1 deletion(-) create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/JMFInit.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/demo/Demo.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioChannel.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioFormatUtils.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioMediaSession.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioReceiver.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/JmfMediaManager.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jspeex/AudioMediaSession.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jspeex/SpeexMediaManager.java create mode 100644 jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/multi/MultiMediaManager.java diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/JMFInit.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/JMFInit.java new file mode 100644 index 000000000..bdc3545fa --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/JMFInit.java @@ -0,0 +1,275 @@ +package org.jivesoftware.smackx.jingle.mediaimpl; + +import com.sun.media.util.Registry; +import com.sun.media.ExclusiveUse; + +import javax.media.format.AudioFormat; +import javax.media.Renderer; +import javax.media.PlugInManager; +import javax.media.Format; +import java.awt.*; +import java.util.Vector; + +public class JMFInit extends Frame implements Runnable { + + private String tempDir = "/tmp"; + + private boolean done = false; + + private String userHome; + + private boolean visible = false; + + public JMFInit(String[] args, boolean visible) { + super("Initializing JMF..."); + + this.visible = visible; + + Registry.set("secure.allowCaptureFromApplets", true); + Registry.set("secure.allowSaveFileFromApplets", true); + + updateTemp(args); + + try { + Registry.commit(); + } + catch (Exception e) { + + message("Failed to commit to JMFRegistry!"); + } + + Thread detectThread = new Thread(this); + detectThread.run(); + + /* + * int slept = 0; while (!done && slept < 60 * 1000 * 2) { try { + * Thread.currentThread().sleep(500); } catch (InterruptedException ie) { } + * slept += 500; } + * + * if (!done) { console.error("Detection is taking too long! + * Aborting!"); message("Detection is taking too long! Aborting!"); } + * + * try { Thread.currentThread().sleep(2000); } catch + * (InterruptedException ie) { } + */ + } + + public void run() { + detectDirectAudio(); + detectS8DirectAudio(); + detectCaptureDevices(); + done = true; + } + + private void updateTemp(String[] args) { + if (args != null && args.length > 0) { + tempDir = args[0]; + + message("Setting cache directory to " + tempDir); + Registry r = new Registry(); + try { + r.set("secure.cacheDir", tempDir); + r.commit(); + + message("Updated registry"); + } + catch (Exception e) { + message("Couldn't update registry!"); + } + } + } + + private void detectCaptureDevices() { + // check if JavaSound capture is available + message("Looking for Audio capturer"); + Class dsauto; + try { + dsauto = Class.forName("DirectSoundAuto"); + dsauto.newInstance(); + message("Finished detecting DirectSound capturer"); + } + catch (ThreadDeath td) { + throw td; + } + catch (Throwable t) { + //Do nothing + } + + Class jsauto; + try { + jsauto = Class.forName("JavaSoundAuto"); + jsauto.newInstance(); + message("Finished detecting javasound capturer"); + } + catch (ThreadDeath td) { + throw td; + } + catch (Throwable t) { + message("JavaSound capturer detection failed!"); + } + + /* + // Check if VFWAuto or SunVideoAuto is available + message("Looking for video capture devices"); + Class auto = null; + Class autoPlus = null; + try { + auto = Class.forName("VFWAuto"); + } + catch (Exception e) { + } + if (auto == null) { + try { + auto = Class.forName("SunVideoAuto"); + } + catch (Exception ee) { + + } + try { + autoPlus = Class.forName("SunVideoPlusAuto"); + } + catch (Exception ee) { + + } + } + if (auto == null) { + try { + auto = Class.forName("V4LAuto"); + } + catch (Exception ee) { + + } + } + try { + Object instance = auto.newInstance(); + if (autoPlus != null) { + Object instancePlus = autoPlus.newInstance(); + } + + message("Finished detecting video capture devices"); + } + catch (ThreadDeath td) { + throw td; + } + catch (Throwable t) { + + message("Capture device detection failed!"); + } + */ + } + + private void detectDirectAudio() { + Class cls; + int plType = PlugInManager.RENDERER; + String dar = "com.sun.media.renderer.audio.DirectAudioRenderer"; + try { + // Check if this is the Windows Performance Pack - hack + cls = Class.forName("VFWAuto"); + // Check if DS capture is supported, otherwise fail DS renderer + // since NT doesn't have capture + cls = Class.forName("com.sun.media.protocol.dsound.DSound"); + // Find the renderer class and instantiate it. + cls = Class.forName(dar); + + Renderer rend = (Renderer) cls.newInstance(); + try { + // Set the format and open the device + AudioFormat af = new AudioFormat(AudioFormat.LINEAR, 44100, 16, + 2); + rend.setInputFormat(af); + rend.open(); + Format[] inputFormats = rend.getSupportedInputFormats(); + // Register the device + PlugInManager.addPlugIn(dar, inputFormats, new Format[0], + plType); + // Move it to the top of the list + Vector rendList = PlugInManager.getPlugInList(null, null, + plType); + int listSize = rendList.size(); + if (rendList.elementAt(listSize - 1).equals(dar)) { + rendList.removeElementAt(listSize - 1); + rendList.insertElementAt(dar, 0); + PlugInManager.setPlugInList(rendList, plType); + PlugInManager.commit(); + // Log.debug("registered"); + } + rend.close(); + } + catch (Throwable t) { + // Log.debug("Error " + t); + } + } + catch (Throwable tt) { + //Do nothing + } + } + + private void detectS8DirectAudio() { + Class cls; + int plType = PlugInManager.RENDERER; + String dar = "com.sun.media.renderer.audio.DirectAudioRenderer"; + try { + // Check if this is the solaris Performance Pack - hack + cls = Class.forName("SunVideoAuto"); + + // Find the renderer class and instantiate it. + cls = Class.forName(dar); + + Renderer rend = (Renderer) cls.newInstance(); + + if (rend instanceof ExclusiveUse + && !((ExclusiveUse) rend).isExclusive()) { + // sol8+, DAR supports mixing + Vector rendList = PlugInManager.getPlugInList(null, null, + plType); + int listSize = rendList.size(); + boolean found = false; + String rname = null; + + for (int i = 0; i < listSize; i++) { + rname = (String) (rendList.elementAt(i)); + if (rname.equals(dar)) { // DAR is in the registry + found = true; + rendList.removeElementAt(i); + break; + } + } + + if (found) { + rendList.insertElementAt(dar, 0); + PlugInManager.setPlugInList(rendList, plType); + PlugInManager.commit(); + } + } + } + catch (Throwable tt) { + //Do nothing + } + } + + private void message(String mesg) { + System.out.println(mesg); + } + + private void createGUI() { + TextArea textBox = new TextArea(5, 50); + add("Center", textBox); + textBox.setEditable(false); + addNotify(); + pack(); + + int scrWidth = (int) Toolkit.getDefaultToolkit().getScreenSize() + .getWidth(); + int scrHeight = (int) Toolkit.getDefaultToolkit().getScreenSize() + .getHeight(); + + setLocation((scrWidth - getWidth()) / 2, (scrHeight - getHeight()) / 2); + + setVisible(visible); + + } + + public static void start(boolean visible) { + new JMFInit(null, visible); + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/demo/Demo.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/demo/Demo.java new file mode 100644 index 000000000..1ee9ec196 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/demo/Demo.java @@ -0,0 +1,156 @@ +package org.jivesoftware.smackx.jingle.mediaimpl.jmf; + +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); + } + + } + +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioChannel.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioChannel.java new file mode 100644 index 000000000..96b03d4a1 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioChannel.java @@ -0,0 +1,454 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: 08/11/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. + */ +package org.jivesoftware.smackx.jingle.mediaimpl.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 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. + *

+ * Send from portA to portB and receive from portB in portA. + *

+ * Sending + * portA ---> portB + *

+ * Receiving + * portB ---> portA + *

+ * Transmit and Receive are interdependents. To receive you MUST trasmit. + * + * @author Thiago Camargo + */ +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 sendStreams = new ArrayList(); + + private boolean started = false; + + /** + * Creates an Audio Channel for a desired jmf locator. For instance: new MediaLocator("dsound://") + * + * @param locator media locator + * @param localIpAddress local IP address + * @param ipAddress remote IP address + * @param localPort local port number + * @param remotePort remote port number + * @param format audio 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. + * + * @return result description + */ + 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 (RTPManager rtpMgr : rtpMgrs) { + rtpMgr.removeReceiveStreamListener(audioReceiver); + rtpMgr.removeSessionListener(audioReceiver); + rtpMgr.removeTargets("Session ended."); + rtpMgr.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) + 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) + 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. + * + * @return description + */ + 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; + + 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 active state + */ + 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 = 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; + } + } + } + + return !failed; + } + + /** + * ************************************************************* + * 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 != null) { + 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(); + } + + } +} \ No newline at end of file diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioFormatUtils.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioFormatUtils.java new file mode 100644 index 000000000..ae5e3541d --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioFormatUtils.java @@ -0,0 +1,55 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: 08/11/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. + */ +package org.jivesoftware.smackx.jingle.mediaimpl.jmf; + +import org.jivesoftware.smackx.jingle.media.PayloadType; + +import javax.media.format.AudioFormat; + +/** + * Audio Format Utils. + * + * @author Thiago Camargo + */ +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 payloadtype + * @return correspondent audioType + */ + public static AudioFormat getAudioFormat(PayloadType payloadtype) { + + switch (payloadtype.getId()) { + case 0: + return new AudioFormat(AudioFormat.ULAW_RTP); + case 3: + return new AudioFormat(AudioFormat.GSM_RTP); + case 4: + return new AudioFormat(AudioFormat.G723_RTP); + default: + return null; + } + + } + +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioMediaSession.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioMediaSession.java new file mode 100644 index 000000000..4a1abce84 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioMediaSession.java @@ -0,0 +1,174 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: 08/11/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. + */ + +package org.jivesoftware.smackx.jingle.mediaimpl.jmf; + +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 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() + * + * @author Thiago Camargo + */ +public class AudioMediaSession extends JingleMediaSession { + + private AudioChannel audioChannel; + private String locator = "javasound://"; + + /** + * 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) { + this(payloadType, remote, local, "javasound://"); + } + + /** + * 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 + * @param locator media locator + */ + public AudioMediaSession(final PayloadType payloadType, final TransportCandidate remote, + final TransportCandidate local, String locator) { + super(payloadType, remote, local); + if (locator != null && !locator.equals("")) + this.locator = locator; + initialize(); + } + + /** + * 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(locator), 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 active state + */ + 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() { + if (audioChannel != null) + 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; + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioReceiver.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioReceiver.java new file mode 100644 index 000000000..72c0c2ce7 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/AudioReceiver.java @@ -0,0 +1,144 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: 08/11/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. + */ + +package org.jivesoftware.smackx.jingle.mediaimpl.jmf; + +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 + * + * @author Thiago Camargo + */ +public class AudioReceiver implements ReceiveStreamListener, SessionListener, + ControllerListener { + + boolean dataReceived = false; + + Object dataSync; + + public AudioReceiver(final 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) { + + 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."); + + } else if (evt instanceof NewReceiveStreamEvent) { + + try { + stream = 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); + } + + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/JmfMediaManager.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/JmfMediaManager.java new file mode 100644 index 000000000..5ac6c91fe --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jmf/JmfMediaManager.java @@ -0,0 +1,127 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: 08/11/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. + */ + +package org.jivesoftware.smackx.jingle.mediaimpl.jmf; + +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 org.jivesoftware.smackx.jingle.mediaimpl.JMFInit; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +/** + * Implements a jingleMediaManager using JMF based API. + * It supports GSM and G723 codecs. + * This API only currently works on windows and Mac. + * + * @author Thiago Camargo + */ +public class JmfMediaManager extends JingleMediaManager { + + private List payloads = new ArrayList(); + + /** + * Creates a Media Manager instance + */ + public JmfMediaManager() { + setupPayloads(); + } + + /** + * Returns a new jingleMediaSession + * + * @param payloadType payloadType + * @param remote remote Candidate + * @param local local Candidate + * @return JingleMediaSession + */ + 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() { + payloads.add(new PayloadType.Audio(3, "gsm")); + payloads.add(new PayloadType.Audio(4, "g723")); + payloads.add(new PayloadType.Audio(0, "PCMU", 16000)); + } + + /** + * Return all supported Payloads for this Manager + * + * @return The Payload List + */ + public List getPayloads() { + return payloads; + } + + /** + * 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 + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jspeex/AudioMediaSession.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jspeex/AudioMediaSession.java new file mode 100644 index 000000000..0fa5aba2c --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jspeex/AudioMediaSession.java @@ -0,0 +1,237 @@ +/** + * $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. + */ + +package org.jivesoftware.smackx.jingle.mediaimpl.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; + +/** + * 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() + * + * @author Thiago Camargo + */ + +public class AudioMediaSession extends JingleMediaSession implements MediaSessionListener { + + private MediaSession mediaSession; + + /** + * Create a Session using Speex Codec + * + * @param localhost localHost + * @param localPort localPort + * @param remoteHost remoteHost + * @param remotePort remotePort + * @param eventHandler eventHandler + * @param quality quality + * @param secure secure + * @param micOn micOn + * @return MediaSession + * @throws NoProcessorException + * @throws UnsupportedFormatException + * @throws IOException + * @throws GeneralSecurityException + */ + 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(); + } + + /** + * 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 active state + */ + 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() { + if (mediaSession != null) + mediaSession.close(); + } + + /** + * 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) { + } + + public void senderReportReceived(SenderReport report) { + } + + public void streamClosed(StreamPlayer stream, boolean timeout) { + } + + /** + * 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; + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jspeex/SpeexMediaManager.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jspeex/SpeexMediaManager.java new file mode 100644 index 000000000..bf6aae6d0 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/jspeex/SpeexMediaManager.java @@ -0,0 +1,114 @@ +/** + * $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. + */ +package org.jivesoftware.smackx.jingle.mediaimpl.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 org.jivesoftware.smackx.jingle.mediaimpl.JMFInit; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +/** + * Implements a jingleMediaManager using JMF based API and JSpeex. + * It supports Speex codec. + * This API only currently works on windows. + * + * @author Thiago Camargo + */ +public class SpeexMediaManager extends JingleMediaManager { + + private List payloads = new ArrayList(); + + 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() { + payloads.add(new PayloadType.Audio(15, "speex")); + } + + /** + * Return all supported Payloads for this Manager + * + * @return The Payload List + */ + public List getPayloads() { + return payloads; + } + + /** + * 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 + } +} diff --git a/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/multi/MultiMediaManager.java b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/multi/MultiMediaManager.java new file mode 100644 index 000000000..929306cc1 --- /dev/null +++ b/jingle/extension/source/org/jivesoftware/smackx/jingle/mediaimpl/multi/MultiMediaManager.java @@ -0,0 +1,84 @@ +/** + * $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. + */ + +package org.jivesoftware.smackx.jingle.mediaimpl.multi; + +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.util.*; + +/** + * Implements a MultiMediaManager using other JingleMediaManager implementations. + * It supports every Codecs that JingleMediaManagers added has. + * + * @author Thiago Camargo + */ + +public class MultiMediaManager extends JingleMediaManager { + + private List managers = new ArrayList(); + + public MultiMediaManager() { + } + + public void addMediaManager(JingleMediaManager manager) { + managers.add(manager); + } + + public void removeMediaManager(JingleMediaManager manager) { + managers.remove(manager); + } + + /** + * Return all supported Payloads for this Manager. + * + * @return The Payload List + */ + public List getPayloads() { + List list = new ArrayList(); + for (JingleMediaManager manager : managers) { + for (PayloadType payloadType : manager.getPayloads()) { + if (!list.contains(payloadType)) + list.add(payloadType); + } + } + return list; + } + + /** + * Returns a new JingleMediaSession + * + * @param payloadType payloadType + * @param remote remote Candidate + * @param local local Candidate + * @return JingleMediaSession JingleMediaSession + */ + public JingleMediaSession createMediaSession(PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local) { + for (JingleMediaManager manager : managers) { + if (manager.getPayloads().contains(payloadType)) { + return manager.createMediaSession(payloadType, remote, local); + } + } + return null; + } +} diff --git a/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleManagerTest.java b/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleManagerTest.java index 0ca56dd4f..02d7c7f95 100644 --- a/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleManagerTest.java +++ b/jingle/extension/test/org/jivesoftware/smackx/jingle/JingleManagerTest.java @@ -66,9 +66,9 @@ 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.*; +import org.jivesoftware.smackx.jingle.mediaimpl.jmf.JmfMediaManager; import org.jivesoftware.smackx.packet.Jingle; import org.jivesoftware.smackx.provider.JingleProvider; -import org.jivesoftware.jingleaudio.jmf.JmfMediaManager; import java.net.DatagramPacket; import java.net.DatagramSocket;