From 3b1b5922465ce0723f5012cc1c8534bc4e639783 Mon Sep 17 00:00:00 2001 From: Damian Minkov Date: Mon, 31 May 2010 07:10:47 +0000 Subject: [PATCH] - Add DTMF for DTMF RFC4733 Listener. - Now we can change the priority of send/receive threads (not activated yet). --- .../impl/neomedia/AudioMediaStreamImpl.java | 63 ++++++- .../impl/neomedia/MediaStreamImpl.java | 41 ++++- .../neomedia/RTPConnectorInputStream.java | 23 ++- .../neomedia/RTPConnectorOutputStream.java | 12 ++ .../impl/neomedia/VideoMediaStreamImpl.java | 10 ++ .../transform/dtmf/DtmfRawPacket.java | 65 +++++++- .../transform/dtmf/DtmfTransformEngine.java | 156 +++++++++++++++++- 7 files changed, 363 insertions(+), 7 deletions(-) diff --git a/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java index 614445281..dccef1a61 100644 --- a/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/AudioMediaStreamImpl.java @@ -6,6 +6,7 @@ */ package net.java.sip.communicator.impl.neomedia; +import java.util.*; import javax.media.*; import javax.media.control.*; import javax.media.format.*; @@ -43,6 +44,11 @@ public class AudioMediaStreamImpl */ private DtmfTransformEngine dtmfTransfrmEngine ; + /** + * List of DTMF listeners; + */ + private List dtmfListeners = new ArrayList(); + /** * List of RTP format strings which are supported by SIP Communicator in * addition to the JMF standard formats. @@ -171,7 +177,10 @@ protected DtmfTransformEngine createDtmfTransformEngine() */ public void addDTMFListener(DTMFListener listener) { - // TODO Auto-generated method stub + if(!dtmfListeners.contains(listener)) + { + dtmfListeners.add(listener); + } } /** @@ -249,7 +258,7 @@ protected void registerCustomCodecFormats(RTPManager rtpManager) */ public void removeDTMFListener(DTMFListener listener) { - // TODO Auto-generated method stub + dtmfListeners.remove(listener); } /** @@ -380,4 +389,54 @@ public void fireConferenceAudioLevelEvent(final long[][] audioLevels) if (csrcAudioLevelListener != null) csrcAudioLevelListener.audioLevelsReceived(audioLevels); } + + /** + * Delivers the DTMF tones. This + * method is meant for use primarily by the transform engine handling + * incoming RTP packets (currently DtmfTransformEngine). + * + * @param tone the new tone + * @param end is end or start of tone. + */ + public void fireDTMFEvent(DTMFTone tone, boolean end) + { + Iterator iter = dtmfListeners.iterator(); + DTMFToneEvent ev = new DTMFToneEvent(this, tone); + while (iter.hasNext()) + { + DTMFListener listener = iter.next(); + if(end) + listener.dtmfToneReceptionEnded(ev); + else + listener.dtmfToneReceptionStarted(ev); + } + } + + /** + * Releases the resources allocated by this instance in the course of its + * execution and prepares it to be garbage collected. + * + * @see MediaStream#close() + */ + public void close() + { + super.close(); + + if(dtmfTransfrmEngine != null) + { + dtmfTransfrmEngine.stop(); + dtmfTransfrmEngine = null; + } + } + + /** + * The priority of the audio is 3, which is meant to be higher than + * other threads and higher than the video one. + * @return audio priority. + */ + @Override + protected int getPriority() + { + return 3; + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java index ba451081d..026872ae9 100644 --- a/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/MediaStreamImpl.java @@ -20,7 +20,6 @@ import com.sun.media.rtp.*; -import net.java.sip.communicator.impl.neomedia.*; import net.java.sip.communicator.impl.neomedia.device.*; import net.java.sip.communicator.impl.neomedia.format.*; import net.java.sip.communicator.impl.neomedia.transform.*; @@ -240,6 +239,18 @@ protected TransformOutputStream createDataOutputStream() configureDataOutputStream(dataOutputStream); return dataOutputStream; } + + @Override + protected TransformInputStream createDataInputStream() + throws IOException + { + TransformInputStream dataInputStream + = super.createDataInputStream(); + + if (dataInputStream != null) + configureDataInputStream(dataInputStream); + return dataInputStream; + } }; this.zrtpControl @@ -262,6 +273,22 @@ protected TransformOutputStream createDataOutputStream() protected void configureDataOutputStream( RTPConnectorOutputStream dataOutputStream) { + dataOutputStream.setPriority(getPriority()); + } + + /** + * Performs any optional configuration on a specific + * RTPConnectorInputStream of an RTPManager to be used by + * this MediaStreamImpl. Allows extenders to override. + * + * @param dataInputStream the RTPConnectorInputStream to be used + * by an RTPManager of this MediaStreamImpl and to be + * configured + */ + protected void configureDataInputStream( + RTPConnectorInputStream dataInputStream) + { + dataInputStream.setPriority(getPriority()); } /** @@ -1808,4 +1835,16 @@ public long[] getRemoteContributingSourceIDs() return remoteSsrcList; } + + /** + * Used to set the priority of the receive/send streams. Underling + * implementations can override this and return different than + * current default value. + * + * @return the priority for the current thread. + */ + protected int getPriority() + { + return Thread.currentThread().getPriority(); + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/RTPConnectorInputStream.java b/src/net/java/sip/communicator/impl/neomedia/RTPConnectorInputStream.java index c26f59042..25f7f53d8 100755 --- a/src/net/java/sip/communicator/impl/neomedia/RTPConnectorInputStream.java +++ b/src/net/java/sip/communicator/impl/neomedia/RTPConnectorInputStream.java @@ -64,6 +64,11 @@ public class RTPConnectorInputStream */ private SourceTransferHandler transferHandler; + /** + * The Thread receiving packets. + */ + private Thread receiverThread = null; + /** * Initializes a new RTPConnectorInputStream which is to receive * packet data from a specific UDP socket. @@ -75,7 +80,8 @@ public RTPConnectorInputStream(DatagramSocket socket) this.socket = socket; closed = false; - new Thread(this).start(); + receiverThread = new Thread(this); + receiverThread.start(); } /** @@ -220,7 +226,7 @@ public int read(byte[] inBuffer, int offset, int length) new IOException("Input buffer not big enough for " + pktLength); System.arraycopy( - pkt.getBuffer(), pkt.getOffset(), inBuffer, offset, pktLength); + pkt.getBuffer(), pkt.getOffset(), inBuffer, offset, pktLength); return pktLength; } @@ -270,4 +276,17 @@ public void setTransferHandler(SourceTransferHandler transferHandler) if (!closed) this.transferHandler = transferHandler; } + + /** + * Changes current thread priority. + * @param priority the new priority. + */ + public void setPriority(int priority) + { + // currently no priority is set +// if(receiverThread != null) +// { +// receiverThread.setPriority(priority); +// } + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/RTPConnectorOutputStream.java b/src/net/java/sip/communicator/impl/neomedia/RTPConnectorOutputStream.java index 5f53ab8c9..2065e3f0d 100755 --- a/src/net/java/sip/communicator/impl/neomedia/RTPConnectorOutputStream.java +++ b/src/net/java/sip/communicator/impl/neomedia/RTPConnectorOutputStream.java @@ -249,6 +249,18 @@ public int write(byte[] buffer, int offset, int length) return length; } + /** + * Changes current thread priority. + * @param priority the new priority. + */ + public void setPriority(int priority) + { + // currently no priority is set +// if(maxPacketsPerMillisPolicy != null && +// maxPacketsPerMillisPolicy.sendThread != null) +// maxPacketsPerMillisPolicy.sendThread.setPriority(priority); + } + /** * Implements the functionality which allows this OutputDataStream * to control how many RTP packets it sends through its diff --git a/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java b/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java index 8c0e9471c..33b056eb7 100644 --- a/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java +++ b/src/net/java/sip/communicator/impl/neomedia/VideoMediaStreamImpl.java @@ -721,4 +721,14 @@ protected void setRemoteSourceID(long ssrc) ((VideoMediaDeviceSession)deviceSession).setRemoteSSRC(ssrc); } + /** + * The priority of the video is 5, which is meant to be higher than + * other threads and lower than the audio one. + * @return video priority. + */ + @Override + protected int getPriority() + { + return 5; + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/transform/dtmf/DtmfRawPacket.java b/src/net/java/sip/communicator/impl/neomedia/transform/dtmf/DtmfRawPacket.java index f4dc45374..d31697f73 100644 --- a/src/net/java/sip/communicator/impl/neomedia/transform/dtmf/DtmfRawPacket.java +++ b/src/net/java/sip/communicator/impl/neomedia/transform/dtmf/DtmfRawPacket.java @@ -20,7 +20,7 @@ * @author Damian Minkov */ public class DtmfRawPacket - extends RawPacket + extends RawPacket { /** * Our class logger. @@ -33,6 +33,21 @@ public class DtmfRawPacket */ public static final int DTMF_PACKET_SIZE = 16; + /** + * The event code to send. + */ + private int code; + + /** + * Is this an end packet. + */ + private boolean end; + + /** + * The duration of the current packet. + */ + private int duration; + /** * Creates a DtmfRawPacket using the specified buffer. * @@ -49,6 +64,23 @@ public DtmfRawPacket(byte[] buffer, int offset, byte payload) setPayload(payload); } + /** + * Used for incoming DTMF packets, creating DtmfRawPacket + * from RTP one. + * @param pkt the RTP packet. + */ + public DtmfRawPacket(RawPacket pkt) + { + super(pkt.getBuffer(), pkt.getOffset(), pkt.getLength()); + + int at = getHeaderLength(); + + code = readByte(at++); + end = (readByte(at++) & 0x80) != 0; + + duration = ((readByte(at++) & 0xFF) << 8) | (readByte(at++) & 0xFF); + } + /** * Initializes DTMF specific values in this packet. * @@ -105,6 +137,10 @@ public void init(int code, */ private void setDtmfPayload(int code, boolean end, int duration) { + this.code = code; + this.end = end; + this.duration = duration; + int at = getHeaderLength(); writeByte(at++, (byte)code); @@ -112,4 +148,31 @@ private void setDtmfPayload(int code, boolean end, int duration) writeByte(at++, (byte)(duration >> 8)); writeByte(at++, (byte)duration); } + + /** + * The event code of the current packet. + * @return the code + */ + public int getCode() + { + return code; + } + + /** + * Is this an end packet. + * @return the end + */ + public boolean isEnd() + { + return end; + } + + /** + * The duration of the current event. + * @return the duration + */ + public int getDuration() + { + return duration; + } } diff --git a/src/net/java/sip/communicator/impl/neomedia/transform/dtmf/DtmfTransformEngine.java b/src/net/java/sip/communicator/impl/neomedia/transform/dtmf/DtmfTransformEngine.java index 485c7ec75..7086718f8 100644 --- a/src/net/java/sip/communicator/impl/neomedia/transform/dtmf/DtmfTransformEngine.java +++ b/src/net/java/sip/communicator/impl/neomedia/transform/dtmf/DtmfTransformEngine.java @@ -24,7 +24,6 @@ public class DtmfTransformEngine implements TransformEngine, PacketTransformer { - /** * Our class logger. */ @@ -73,6 +72,22 @@ private enum ToneTransmissionState END_SEQUENCE_INITIATED }; + /** + * Array of all supported tones. + */ + private static final DTMFTone[] supportedTones = + new DTMFTone[] + {DTMFTone.DTMF_0, DTMFTone.DTMF_1, DTMFTone.DTMF_2, DTMFTone.DTMF_3, + DTMFTone.DTMF_4, DTMFTone.DTMF_5, DTMFTone.DTMF_6, DTMFTone.DTMF_7, + DTMFTone.DTMF_8, DTMFTone.DTMF_9, DTMFTone.DTMF_A, DTMFTone.DTMF_B, + DTMFTone.DTMF_C, DTMFTone.DTMF_D, + DTMFTone.DTMF_SHARP, DTMFTone.DTMF_STAR}; + + /** + * The dispatcher that is delivering tones to the media steam. + */ + private DTMFDispatcher dtmfDispatcher = null; + /** * The status that this engine is currently in. */ @@ -161,6 +176,15 @@ public RawPacket reverseTransform(RawPacket pkt) if(currentDtmfPayload == pkt.getPayloadType()) { + DtmfRawPacket p = new DtmfRawPacket(pkt); + + if (dtmfDispatcher == null) + { + dtmfDispatcher = new DTMFDispatcher(); + new Thread(dtmfDispatcher).start(); + } + dtmfDispatcher.addTonePacket(p); + // ignore received dtmf packets // if jmf receive change in rtp payload stops reception return null; @@ -293,4 +317,134 @@ public void stopSendingDTMF() toneTransmissionState = ToneTransmissionState.END_REQUESTED; } + /** + * Stops threads that this transform engine is using for even delivery. + */ + public void stop() + { + if(dtmfDispatcher != null) + dtmfDispatcher.stop(); + } + + /** + * A simple thread that waits for new tones to be reported from incoming + * RTP packets and then delivers them to the AudioMediaStream + * associated with this engine. The reason we need to do this in a separate + * thread is of course the time sensitive nature of incoming RTP packets. + */ + private class DTMFDispatcher + implements Runnable + { + /** Indicates whether this thread is supposed to be running */ + private boolean isRunning = false; + + /** The tone that we last received from the reverseTransform thread*/ + private DTMFTone lastReceivedTone = null; + + /** The tone that we last received from the reverseTransform thread*/ + private DTMFTone lastReportedTone = null; + + /** + * Have we received end of the currently started tone. + */ + private boolean toEnd = false; + + /** + * Waits for new tone to be reported via the addTonePacket() + * method and then delivers them to the AudioMediaStream that + * we are associated with. + */ + public void run() + { + isRunning = true; + + DTMFTone temp = null; + + while(isRunning) + { + synchronized(this) + { + if(lastReceivedTone == null) + { + try + { + wait(); + } + catch (InterruptedException ie) {} + } + + temp = lastReceivedTone; + // make lastReportedLevels to null + // so we will wait for the next tone on next iteration + lastReceivedTone = null; + } + + if(temp != null + && ((lastReportedTone == null && !toEnd) + || (lastReportedTone != null && toEnd))) + { + //now notify our listener + if (mediaStream != null) + { + mediaStream.fireDTMFEvent(temp, toEnd); + if(toEnd) + lastReportedTone = null; + else + lastReportedTone = temp; + toEnd = false; + } + } + } + } + + /** + * A packet that we should convert to tone and deliver + * to our media stream and its listeners in a separate thread. + * + * @param p the packet we will convert and deliver. + */ + public void addTonePacket(DtmfRawPacket p) + { + synchronized(this) + { + this.lastReceivedTone = getToneFromPacket(p); + this.toEnd = p.isEnd(); + + notifyAll(); + } + } + + /** + * Causes our run method to exit so that this thread would stop + * handling levels. + */ + public void stop() + { + synchronized(this) + { + this.lastReceivedTone = null; + isRunning = false; + + notifyAll(); + } + } + + /** + * Maps DTMF packet codes to our DTMFTone objects. + * @param p the packet + * @return the corresponding tone. + */ + private DTMFTone getToneFromPacket(DtmfRawPacket p) + { + for (int i = 0; i < supportedTones.length; i++) + { + DTMFTone t = supportedTones[i]; + if(t.getCode() == p.getCode()) + return t; + } + + return null; + } + } + }