package iax.protocol.call; import iax.audio.AudioListener; import iax.audio.Player; import iax.audio.PlayerException; import iax.audio.Recorder; import iax.audio.RecorderException; import iax.audio.ulaw.ULAWPlayer; import iax.audio.ulaw.ULAWRecorder; import iax.protocol.call.command.send.CallCommandSendFacade; import iax.protocol.call.state.CallState; import iax.protocol.call.state.Initial; import iax.protocol.frame.Frame; import iax.protocol.frame.FullFrame; import iax.protocol.frame.MiniFrame; import iax.protocol.frame.ProtocolControlFrame; import iax.protocol.peer.Peer; import iax.protocol.peer.PeerException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Timer; import java.util.TimerTask; /** * Class that encapsulates the funcionality of a iax call and implements the interface AudioListener * for playing and recording audio. */ public class Call implements AudioListener { /** * Interval in milliseconds that the miniframe's timestamp is reset */ public static final int TIMESTAMP_MINIFRAME_RESET = 65536; // Maximun size of a sequence number private final int SEQNO_MAXSIZE = 256; // ***** PING DESACTIVATED ***** //Peer's ping refresh in seconds //private final int PING_REFRESH = 5; //Call's retry max private final int RETRY_MAXCOUNT = 5; //Peer's retry refresh in seconds private final int RETRY_REFRESH = 1; // HashMap with the frames that are waiting for an ack private HashMap<Long, FullFrame> framesWaitingAck; // HashMap with the frames that are waiting for a specific reply private HashMap framesWaitingReply; // Audio player private Player player; // Audo Recorder private Recorder recorder; // Flag to known if the audio is being played or not private boolean playing = false; // Actual call's state private CallState state; // Source call number of this call (number to identify this call) private int srcCallNo; // Destination call number of this call (number got from the remote call) private int destCallNo; // Source timestamp for full frames from the first full frame sent by this call private long srcTimestamp; // Source timestamp for mini frames from the first mini frame sent by this call or the last mini frame's timestamp reset private long srcTimestampMiniFrame; // Oubound sequence number. private int oseqno; // Inbound sequence number. private int iseqno; // Called number (number or extension's indentifier) private String calledNumber; // Flag to known if the firstVoiceFrame was sent or not private boolean firstVoiceFrameSent; // Peer that handles this call private Peer peer; // Ping timer task to send ping frames private Timer pingTimer; // Retry timer task to retry frames not commited whith a specific frame or an ack frame private Timer retryTimer; //Flag to determinate if the call is hold or not private boolean hold; //Flag to determinate if the call is mute or not private boolean mute; //Audio Streams private OutputStream outStream; private InputStream inStream; /** * Constructor. Initialize the player and the recorder * @param peer the peer that handles this call * @param srcCallNo source call number of this call * @throws CallException if an error occurred */ public Call(Peer peer, int srcCallNo, OutputStream outStream, InputStream inStream) throws CallException { this.peer = peer; this.srcCallNo = srcCallNo; this.hold = false; this.mute = false; this.framesWaitingAck = new HashMap<Long, FullFrame>(); this.framesWaitingReply = new HashMap(); this.outStream = outStream; this.inStream = inStream; try { //this.player = new GSMPlayer(); //this.recorder = new GSMRecorder(); this.player = new ULAWPlayer(outStream); this.recorder = new ULAWRecorder(this, inStream); } catch (Exception e) { throw new CallException(e); } } /** * Gets the peer that handles this call * @return the peer that handles this call */ public Peer getPeer() { return peer; } /** * Gets the source call number of this call * @return the source call number of this call */ public int getSrcCallNo() { return srcCallNo; } /** * Gets the destination call number of this call * @return the destination call number of this call */ public int getDestCallNo() { return destCallNo; } /** * Gets the inbound sequence number of this call * @return the inbound sequence number of this call */ public int getIseqno() { return iseqno; } /** * Gets the outbound sequence number of this call * @return the outbound sequence number of this call */ public int getOseqno() { return oseqno; } /** * Gets the called number (number or extension's identifier) of this call * @return the called number (number or extension's identifier) of this call */ public String getCalledNumber() { return calledNumber; } /** * Gets the timestamp from the first full frame sent * @return the timestamp from the first full frame sent */ public long getTimestamp() { long now = System.currentTimeMillis(); long fullFrameTimestamp = now - srcTimestamp; //fullFrameTimestamp = now; miniFrameTimestamp = fullFrameTimestamp; return fullFrameTimestamp; } //private long fullFrameTimestamp = System.currentTimeMillis(); private long miniFrameTimestamp = srcTimestampMiniFrame; /** * Gets the timestamp from the first mini frame sent or the last reset * @return the timestamp from the first mini frame sent or the last reset */ public long getTimestampMiniFrame() { miniFrameTimestamp += 20; return miniFrameTimestamp; } /** * Updates the inbound sequence number with the number passed in the argumens * @param value to update the inbound sequence number */ private synchronized void incIseqno(int value) { iseqno = value; if (iseqno == SEQNO_MAXSIZE) iseqno = 0; } /** * Updates the outbound sequence number with the number passed in the argumens * @param value to update the outbound sequence number */ private synchronized void incOseqno(int value) { oseqno = value; if (oseqno == SEQNO_MAXSIZE) oseqno = 0; } /** * Gets if the first voice frame was sent or not * @return true if the first voice frame was sent, or false if not */ public boolean isFirstVoiceFrameSent() { return firstVoiceFrameSent; } /** * Sets that the first voice frame was sent */ public void firstVoiceFrameSent() { this.firstVoiceFrameSent = false; } /** * Gets if the call is hold or not * @return true if the call is hold, false if not */ public boolean isHold() { return hold; } /** * Gets if the call is mute or not * @return true if the call is mute, false if not */ public boolean isMute() { return hold; } /** * Starts the call. For that initialize the sequence's numbers of this call and the destination call number to zero, * the flag to known if the first voice frame was sent to false, the call's state to initial and the sources timestamps * to the actual timestamp. Also starts the timers (ping and retry) * @param calledNumber the called number (number or extension's identifier) of this call */ public void startCall(String calledNumber) { this.iseqno = 0; this.oseqno = 0; this.destCallNo = 0; this.firstVoiceFrameSent = true; this.framesWaitingAck.clear(); this.calledNumber = calledNumber; this.state = Initial.getInstance(); this.srcTimestamp = System.currentTimeMillis(); this.srcTimestampMiniFrame = srcTimestamp; // ***** PING DESACTIVATED ***** // this.pingTimer = new Timer(); // TimerTask pingTimerTask = new TimerTask() { // public void run() { // ping(); // } // }; // pingTimer.schedule(pingTimerTask, PING_REFRESH*1000, PING_REFRESH*1000); this.retryTimer = new Timer(); /******RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR TimerTask retryTimerTask = new TimerTask() { public void run() { retryFramesWaiting(); } }; retryTimer.schedule(retryTimerTask, RETRY_REFRESH * 1000, RETRY_REFRESH * 1000);*/ } /** * Notifies an answered */ public void answeredCall() { peer.answeredCall(this); startRecorder(); } /** * Notifies a received ringing for playing wait tones */ public void ringingCall() { peer.ringingCall(this); } /** * Notifies a received stop ringing for stopping wait tones * (STOP SOUNDS DOESN'T EXIST IN THE DRAFT) */ public void stopRingingCall() { peer.stopRingingCall(this); } /** * Saves the destination call number of this call to the destination call number passed in the arguments, and notifies * to the peer for the binding of this call * @param destCallNo the destination call number of this call */ public void bindCall(int destCallNo) { this.destCallNo = destCallNo; peer.bindCall(this); } /** * Ends a call. For that stops the player and the recorder, and notifies the peer that this call is finished */ public void endCall() { // ***** PING DESACTIVATED ***** // pingTimer.cancel(); retryTimer.cancel(); player.stop(); recorder.stop(); peer.endCall(this); } /** * Holds a call. For that stops the recorder and the player */ public void holdCall() { if (!hold) { hold = true; player.stop(); try { //player = new GSMPlayer(); player = new ULAWPlayer(outStream); } catch (PlayerException e) { e.printStackTrace(); } if (!mute) { recorder.stop(); try { //recorder = new GSMRecorder(); recorder = new ULAWRecorder(this, inStream); } catch (RecorderException e) { e.printStackTrace(); } } } } /** * Unholds a call. For that starts the recorder and the player */ public void unHoldCall() { if (hold) { player.play(); if (!mute) { recorder.record(this); } hold = false; } } /** * Mutes a call. For that stops the recorder. */ public void muteCall() { if (!mute) { mute = true; recorder.stop(); try { //recorder = new GSMRecorder(); recorder = new ULAWRecorder(this, inStream); } catch (RecorderException e) { e.printStackTrace(); } } } /** * Unmutes a call. For that starts the recorder. */ public void unMuteCall() { if (mute) { recorder.record(this); mute = false; } } /** * Resets the mini frame's source timestamp becouse of the overflow, that is the base for calculating the mini frame's * timestamp. */ public synchronized void resetTimestampMiniFrame() { srcTimestampMiniFrame = System.currentTimeMillis(); } /** * Send a ping */ public void ping() { CallCommandSendFacade.ping(this); } /** * Sets the call's state to the state passed in the arguments * @param state the new call's state */ public void setState(CallState state) { this.state = state; } /** * Handles the full frame received. For that update the outbound sequence number of this call to the inbound sequence * number of the full frame and, if it isn't an ack, the inbound sequence number of this call to the outbound sequence * number of the full frame + 1. After that, delegates in the call's state for handling the full frame. * @param fullFrame the full frame received */ public void handleRecvFrame(FullFrame fullFrame) { if (fullFrame.getFrameType() != FullFrame.PROTOCOLCONTROLFRAME_T) { incIseqno(fullFrame.getOseqno() + 1); } else if (fullFrame.getSubclass() != ProtocolControlFrame.ACK_SC) { incIseqno(fullFrame.getOseqno() + 1); } incOseqno(fullFrame.getIseqno()); state.handleRecvFrame(this, fullFrame); } /** * Handles the mini frame received. For that, delegates in the call's state for handling the mini frame. * @param frame the mini frame received */ public void handleRecvFrame(MiniFrame frame) { state.handleRecvFrame(this, frame); } /** * Handles the sending of a frame. For that, delegates in the call's state for handling this sending. * @param frame the frame to send */ public void handleSendFrame(Frame frame) { state.handleSendFrame(this, frame); } /** * Ackes a full frame waiting for that removing it from the list of full frames waiting for ack * @param timeStamp the timestamp of the full frame acked */ public synchronized void ackedFrame(int oSeqNumber) { for (FullFrame frame : framesWaitingAck.values()) { if (frame.getIseqno() == oSeqNumber) { framesWaitingAck.remove(frame.getTimestamp()); break; } } } /** * Replied a full frame waiting for that removing it from the list of full frames waiting for reply * @param id the id of the full frame reply */ public synchronized void repliedFrame(int id) { framesWaitingReply.remove(id); } /** * Sends a frame without wait reply or ack. For that delegates in the iax peer. * @param frame the mini frame to send */ public void sendFrameAndNoWait(Frame frame) { peer.sendFrame(frame); } /** * Sends a full frame that needs to be acked. For that added it to the list of frame waiting to be acked and after that * delegates in the iax peer to send it * @param fullFrame the full frame to send */ public synchronized void sendFullFrameAndWaitForAck(FullFrame fullFrame) { framesWaitingAck.put(fullFrame.getTimestamp(), fullFrame); peer.sendFrame(fullFrame); } /** * Sends a full frame that needs to be replied. For that added it to the list of frame waiting to be replied and after that * delegates in the iax peer to send it * @param fullFrame the full frame to send */ public synchronized void sendFullFrameAndWaitForRep(FullFrame fullFrame) { framesWaitingReply.put(fullFrame.getSubclass(), fullFrame); peer.sendFrame(fullFrame); } /** * Plays audio from the audio frames received through the player. If the player is stopped, starts it. * @param timestamp the timestamp of the frame * @param data the audio data of the frame * @param absolute if the timestamp absolute or not */ public void writeAudioIn(long timestamp, byte[] data, boolean absolute) { if (!hold) { player.write(timestamp, data, absolute); if (!playing) { player.play(); playing = true; } } } /** * Starts to record audio from the microphone through the recorder */ public void startRecorder() { recorder.record(this); } // Method to retry frames that haven't been commited whith a specific frame or an ack frame private synchronized void retryFramesWaiting() { try { Iterator iterator = framesWaitingAck.values().iterator(); while (iterator.hasNext()) { FullFrame retryFullFrame = (FullFrame) iterator.next(); retryFullFrame.incRetryCount(); if (retryFullFrame.getRetryCount() < RETRY_MAXCOUNT) { peer.sendFrame(retryFullFrame); } else throw new PeerException("Reached retries maximum (" + RETRY_MAXCOUNT + ") in the call " + srcCallNo + " for a full frame of type " + retryFullFrame.getFrameType() + ", subclass " + retryFullFrame.getSubclass()); } iterator = framesWaitingReply.values().iterator(); while (iterator.hasNext()) { FullFrame retryFullFrame = (FullFrame) iterator.next(); retryFullFrame.incRetryCount(); if (retryFullFrame.getRetryCount() < RETRY_MAXCOUNT) { peer.sendFrame(retryFullFrame); } else throw new PeerException( "Reached retries maximun in the call " + srcCallNo + " for a full frame of type " + retryFullFrame.getFrameType() + ", subclass " + retryFullFrame.getSubclass()); } } catch (PeerException e) { framesWaitingAck.clear(); framesWaitingReply.clear(); endCall(); e.printStackTrace(); } } public void listen(byte[] buffer, int pos, int length) { //byte[] audioBuffer = new byte[length]; byte[] audioBuffer = new byte[length - pos]; //System.arraycopy(buffer, pos, audioBuffer, 0, length); System.arraycopy(buffer, pos, audioBuffer, 0, length - pos); CallCommandSendFacade.sendVoice(this, audioBuffer); } }