Package org.asteriskjava.iax.protocol

Source Code of org.asteriskjava.iax.protocol.Call

package org.asteriskjava.iax.protocol;


import org.asteriskjava.iax.audio.javasound.AudioInterface;
import org.asteriskjava.iax.util.ByteBuffer;

import java.io.IOException;
import java.util.ArrayList;

/**
* Call deals with all the packets that are part of a specific call (or
* registration).
*/
/*
The thing to remember is that a _received_ message
contains fields with the _senders_ viewpoint
so _source_ is the far end and _dest_ is us.
in the reply the oposite is true :
_source_ is us and _dest_ is them.
*/
public class Call
        implements Runnable {

    // Represent the 'Source Call Number' and 'Destination Call Number'
    // in the FullFrame header. They uniquely identify the call between
    // two parties.
    // Who's who depends on the directions:
    // outgoing:
    // _lno = Source Call Number
    // _rno = Destination Call Number
    private Character _lno;
    private Character _rno;
    /**
     * Our peer.
     */
    protected Friend _peer;
    /**
     * The queue of incoming frames.
     */
    protected ArrayList<byte[]> _frameQueue;
    /**
     * The receiving process thread.
     */
    protected Thread _process;
    private int _oseq = 0;
    private int _iseq = 0;
    private int _ackedTo = 0;
    private boolean _done = false;
    private long _startStamp = 0;
    private String _password;
    private boolean _registered;
    // The CALLED NUMBER, CALLING NUMBER and CALLING NAME for the IE
    private String _farNo = null;
    private String _nearNo = null;
    private String _farName = null;
    private String _nearName = null;
    private boolean _accepted;
    private AudioSender _say;
    private boolean _answered;
    private Thread _retry;
    private String _username;
    private int _refresh = 60;
    private boolean _callIsInbound;
    private FullFrame[] _outbound = new FullFrame[256];
    private boolean _forReg = false;
    private boolean _forUnReg = false;
    private AudioInterface _audio;
    private int _format;
    private boolean _recvdFirstAudioFrame = false;
    private boolean _timedout = false;
    private int _hungupCauseCode;
    private long _stampTopWord = 0;
    private long _oldStamp = 0;
    Ringer ringer = null;

    /**
     * The outbound constructor for Call. We know nothing except where
     * to send it.
     *
     * @param source Our peer
     * @see #startRcv
     */
    // Birgit, is it really only outbound?
    public Call(Friend source) {
        _peer = source;
        _callIsInbound = false;
        _lno = _peer.getFreeCallNo();
        this.startRcv();
    }

    /**
     * The constructor for Call.
     *
     * @param friend        Our peer
     * @param forRegister   If this is a registration call (true) or not (false)
     * @param forUnregister If this is a unregistration call (true) or not (false)
     */
    public Call(Friend friend, boolean forRegister, boolean forUnregister) {
        this(friend);
        _forReg = forRegister;
        _forUnReg = forUnregister;
    }

    /**
     * Returns if this a registration call.
     *
     * @return True if a registration call, false if not
     */
    public boolean isForReg() {
        return _forReg;
    }

    /**
     * Returns if this a unregistration call.
     *
     * @return True if a unregistration call, false if not
     */
    public boolean isForUnReg() {
        return _forUnReg;
    }

    /**
     * Sets if this is an inbound or outbound call.
     *
     * @param b True if inbound, false if outbound
     */
    public void setIsInbound(boolean b) {
        _callIsInbound = b;
    }

    /**
     * Returns if this is an inbound Call or outbound
     *
     * @return True if inbound, false if outbound
     */
    public boolean getIsInbound() {
        return _callIsInbound;
    }

    /**
     * This method starts the bi-directional datastream. It kicks off
     * the process thread (see run()) for inbound frames and a
     * retry thread for outbound frames.
     *
     * @see #run()
     */
    private synchronized void startRcv() {
        _frameQueue = new ArrayList<byte[]>();
        _process = new Thread(this, "call-" + (int) _lno.charValue() + "-rcv");
        _process.setPriority(Thread.MAX_PRIORITY - 1);
//        _process.setDaemon(true);

        resetClock();
        // Birgit: Tim mentioned using TimerTask to resend
        Runnable retry = new Runnable() {

            public void run() {
                while (!_done) {
                    resendUnacked();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ex) {
                        ; // dont care
                    }
                }
            }
        };
        _retry = new Thread(retry, "call-" + (int) _lno.charValue() + "-retry");
        _retry.setPriority(Thread.MIN_PRIORITY);
//        _retry.setDaemon(true);
        _process.start();
        _retry.start();
    }

    /**
     * Adds an incoming frame (as bytes) to the frame queue. We are
     * still on the recv thread; this data was received by binder and
     * passed to us via friend. The frames are stored in the queue and
     * we deal with them on our own thread to relief the recv thread.
     * In other words, this is the last thing we do on the recv thread!
     *
     * @param data The frame
     * @see #run()
     */
    public synchronized void addFrame(byte[] data) {
        _frameQueue.add(data);
        this.notifyAll();
    }

    /**
     * Manages the incoming frames stored in the frame queue.
     * This thread is started by startRcv() and run separately from the
     * binder's recv thread.
     *
     * @todo sort the frames into sequence before we dispose of them
     * @see #startRcv()
     */
    public void run() {
        while (!_done) {
            Object[] frames = new Object[0];
            synchronized (this) {
                try {
                    this.wait();
                } catch (InterruptedException ex) {
                    ; // don't care
                }
                int sz;
                // do some smart stuff here? - limit the take to
                // only 20 frames ?
                // you'd hope that normally we'd get 1 or maybe
                // 2 frames here.
                if ((sz = _frameQueue.size()) > 0) {
                    frames = new Object[sz];
                    for (int i = 0; i < sz; i++) {
                        frames[i] = _frameQueue.get(i);
                    }
                    _frameQueue.clear();
                }
            }
            // now released the lock so let's deal with the list.
            // we are now on the thread of the call, any time we waste is
            // our own.
            // should really sort these into sequence before we dispose of them.
            if (frames.length > 1) {
                // sort them ...
            }
            for (int i = 0; i < frames.length; i++) {
                try {
                    frameDeal((byte[]) frames[i]);
                } catch (Throwable ex1) {
                    ex1.printStackTrace();
                }
            }
        }

        // clean up here ....
        Log.debug("In call Clean up");

        if (_audio != null) {
            _audio.stopPlay();
            _audio.stopRec();
            _audio = null;

        }

    }

    /**
     * Deal with newly received frame bytes. This method turns them
     * into a Full or Mini frame, deal with internal counters, sends an
     * acknowledgement and notifies the frame it has arrived.
     *
     * @param bs byte[]
     * @throws IAX2ProtocolException Thrown by Frame.arrived()
     * @see #impliedAck
     * @see Frame#arrived
     */
    void frameDeal(byte[] bs) throws IAX2ProtocolException {
        Frame f = null;
        if (bs[0] < 0) {
            // full frame
            FullFrame ff = FullFrame.create(this, bs);
            impliedAck(ff);
            if (!ff.isAck()) {
                f = addIn(ff);
            } else {
                Log.debug("ignoring oseq in ack");
            }
            ff.ack();
        } else {
            f = new MiniFrame(this, bs);
            //Log.warn("Mini Frame");
        }
        if (f != null) {
            f.arrived();
        }
    }

    /**
     * Handles the implied Ack.
     * Any full frame constitutes an ack of sorts. The
     * _iseq in an inbound message is (1+ our _oseq) for the last message
     * they got. So we run through the ring from the last highwater
     * mark removing outbound frames.
     *
     * @param ff The last full frame we received
     */
    private synchronized void impliedAck(FullFrame ff) {
        int happyto = ff._iseq;
        if (happyto < _ackedTo) {
            Log.debug("ack wrap " + happyto + " < " + _ackedTo);
            for (int i = _ackedTo; i < 256; i++) {
                ackIt(i, ff);
            }
            for (int i = 0; i < happyto; i++) {
                ackIt(i, ff);
            }
        }
        for (int i = _ackedTo; i < happyto; i++) {
            ackIt(i, ff);
        }
        _ackedTo = happyto;
    }

    /**
     * We received an implied or explicit ack to the frame we send.
     * Commit it and remove from our outbound array.
     *
     * @param i   The index of the frame in the outbound array
     * @param ack The frame that is acked
     */
    private void ackIt(int i, FullFrame ack) {
        if (_outbound[i] != null) {
            _outbound[i].commit(ack);
            _outbound[i] = null;
        }
    }

    /**
     * Generates a new outbound stream sequence number, oSeqno.
     *
     * @return A new outbound stream sequence number
     */
    synchronized int getOseqInc() {
        // look at this .....
        int nos = _oseq++;
        nos = nos % 256;
        return nos;
    }

    /**
     * Returns the outbound stream sequence number, oSeqno.
     *
     * @return The outbound stream sequence number
     */
    synchronized int getOseq() {
        return _oseq;
    }

    /**
     * Returns the inbound stream sequence number, iSeqNo.
     *
     * @return The inbound stream sequence number
     */
    int getIseq() {
        return _iseq;
    }

    /**
     * Sets the inbound stream sequence number, iSeqNo.
     *
     * @param next The inbound stream sequence number
     */
    void setIseq(int next) {
        _iseq = next % 256;
    }

    /**
     * Sends a frame to our peer.
     *
     * @param bs The frame (in bytes)
     * @see Friend#send(ByteBuffer)
     */
    public void send(ByteBuffer bs) {
        if (!_done) {
            _peer.send(bs);
        }
    }

    /**
     * Starts a new outbound call.
     *
     * @param username    Username (peer or user) for authentication
     * @param password    Password for authentication
     * @param calledNo    Number/extension to call
     * @param callingNo   Number/extension we call from
     * @param callingName Name of the person calling
     */
    public void newCall(String username, String password, String calledNo,
                        String callingNo, String callingName) {
        // it is a _new_ call so reset the clock.
        resetClock();
        ProtocolControlFrameNew cal = new ProtocolControlFrameNew(this);
        _password = password;
        _farNo = calledNo;
        _nearNo = callingNo;
        _nearName = callingName;
        cal.sendNew(_lno, username, calledNo, callingNo, callingName);
    }

    /**
     * Returns the password for authentication
     *
     * @return The password
     */
    String getPassword() {
        return _password;
    }

    /**
     * Returns the timestamp of this call. This is the number of
     * milliseconds since the call started.
     *
     * @return The timestamp
     */
    public int getTimestamp() {
        long now = System.currentTimeMillis();
        return (int) (now - _startStamp);
    }

    /**
     * Removes this call because it has finished or torn down.
     */
    public synchronized void removeSelf() {
        Log.debug("Removing call " + (int) _lno.charValue());
        _peer.removeCall(this);
        _done = true;
        this.notifyAll();
        if (_audio != null) {
            _audio.stopPlay();
            _audio.stopRinging();
            if (ringer != null) {
                ringer.stop();
            }
            _audio.stopRec();
        }
    }

    /**
     * Sets if this (outbound) call is registered or not. This will be
     * called if REGACK is received from the other end or a REGREJ.
     *
     * @param b True if registered, false if not.
     */
    void setRegistered(boolean b) {
        _registered = b;
        this._peer.setRegistered(b);
        Log.debug("Call registered = " + b);
    }

    boolean getRegistered() {
        return _registered;
    }

    /**
     * Sets if this (outbound) call is accepted or not.  This bit of
     * information comes in with a received ACCEPT or REJECT.
     *
     * @param b True if accepted, false it not
     */
    void setAccepted(boolean b) {
        _accepted = b;
    }

    boolean getAccepted() {
        return _accepted;
    }

    /**
     * Starts sending our audio recording. This method creates a new
     * AudioSender object to do that.
     *
     * @see #setAnswered(boolean)
     * @see AudioSender#AudioSender(AudioInterface, Call)
     */
    private void startAudioRec() {
        _say = new AudioSender(_audio, this);
        _audio.startRec();
        _audio.setAudioSender(_say);
    }

    /**
     * Sets if this call is answered.
     * This can either be when we receive a ANSWER frame from our peer
     * to an outbound call, or when we answer an incoming call ourselves.
     *
     * @param b True is answered, false if not
     */
    void setAnswered(boolean b) {
        if (!_answered && b) {
            _audio.stopRinging();
            if (ringer != null) {
                ringer.stop();
            }
            startAudioRec();
        }
        _answered = b;
        _peer.setAnswered(this);
    }

    public int getHungupCauseCode() {
        return _hungupCauseCode;
    }

    /**
     * Our peer has hung up. We'll remove ourself.
     * This bit of information comes in with a received HANGUP frame.
     *
     * @see #removeSelf()
     * @see #hangup
     */
    void hungup(int causecode) {
        _hungupCauseCode = causecode;
        removeSelf();
    }

    /**
     * Writes audio to the speaker.
     *
     * @param bs The incoming audio
     * @param ts The timestap
     * @throws IOException Description of Exception
     * @see VoiceFrame#arrived()
     * @see MiniFrame#arrived()
     */
    public void audioWrite(byte[] bs, long ts) throws IOException {
        if (_audio != null) {
            long stamp = _stampTopWord + ts;
            if ((stamp < _oldStamp) && (50000 < (_oldStamp - stamp))) {
                Log.debug("Wrapped timestamp on rcv");
                _stampTopWord += 0x10000;
                stamp = _stampTopWord + ts;
                Log.debug("New timestamp top bits are " + _stampTopWord);
            }
            _oldStamp = stamp;
            _audio.write(bs, stamp);
        }
    }

    /**
     * Notifies us that a VOICE FullFrame has been received.
     *
     * @see VoiceFrame#arrived()
     */
    public void fullVoiceFrameRcvd(long stamp) {
        _stampTopWord = stamp & 0xffff0000;
        Log.debug("New timestamp top bits are " + _stampTopWord);

        if ((_audio != null) && (!_recvdFirstAudioFrame)) {
            _recvdFirstAudioFrame = true;
            _audio.stopRinging();
            if (ringer != null) {
                ringer.stop();
            }
        }
    }

    /**
     * Adds a full frame to the outbound buffer that hasn't been
     * acknowledges yet. This is done so it can be resend when an
     * acknowledgement doesn't arrive (in time).
     *
     * @param outb FullFrame
     */
    synchronized void addUnacked(FullFrame outb) {
        int where = outb._oseq;
        _outbound[where] = outb;
    }

    /**
     * Returns the local call number. Together with the remote call
     * number, they uniquely identify the call between two parties.
     * <p/>
     * On an outgoing call this represents 'Source Call Number',
     * on an incoming call this represents 'Destination Call Number'.
     *
     * @return The local call number
     */
    public Character getLno() {
        return this._lno;
    }

    /**
     * Returns the remote call number. Together with the local call
     * number, they uniquely identify the call between two parties.
     * <p/>
     * On an outgoing call this represents 'Destination Call Number',
     * on an ingoing call this represents 'Source Call Number'.
     *
     * @return The remote call number
     */
    public Character getRno() {
        return _rno;
    }

    /**
     * Returns the timestamp.
     *
     * @return The timestamp
     */
    public long getStartTimestamp() {
        return this._startStamp;
    }

    /**
     * Sets the remote call number as a character.  This bit of
     * information comes in with a received ACCEPT.
     *
     * @param d Character
     * @see #getRno
     */
    public void setRno(Character d) {
        _rno = d;
    }

    /**
     * Resets the clock. This method sets the start timestamp of a
     * new call.
     *
     * @see ProtocolControlFrameNew#ProtocolControlFrameNew(Call)
     */
    void resetClock() {
        _startStamp = System.currentTimeMillis();
    }

    /**
     * Sets the username and password for authentication
     *
     * @param username The username
     * @param password The password
     */
    public void setUnameNpass(String username, String password) {
        _password = password;
        _username = username;
    }

    /**
     * Sends a register request and let the rest flow from there.
     */
    void register() {
        ProtocolControlFrame regreq = new ProtocolControlFrame(this);
        _refresh = 60;
        regreq.sendRegReq(_username, _refresh);
    }

    /**
     * Sends a unregister request and let the rest flow from there.
     */
    void unregister() {
        ProtocolControlFrame regrel = new ProtocolControlFrame(this);
        regrel.sendRegRel(_username);
    }

    /**
     * Sends a poke message to test connectivity of a remote IAX peer.
     */
    void sendPoke() {
        ProtocolControlFrame pokereq = new ProtocolControlFrame(this);
        resetClock();
        pokereq.sendPoke();
    }

    /**
     * Sets if we received a pong to our poke (or ping).
     *
     * @param b True if we received pong, false otherwise
     */
    void setPong(boolean b) {
        int roundtrip = getTimestamp();
        _peer.setPong(b, roundtrip);
    }

    /**
     * Resends the unacknowledged frames, that haven't expired their
     * maximum number of retries.
     * If an unacknowledged frame cannot be resend anymore (i.e. it has
     * been sent max retries), we'll tear down the connection.
     * <p/>
     * <p>
     * Draft - 7. Message Transport: <br/>
     * If no acknowledgment is received after a locally configured
     * number of retries, default 4, the call leg SHOULD be considered
     * unusable and the call MUST be torn down without any further
     * interaction on this call leg.
     * </p>
     */
    void resendUnacked() {
        int osq = this._oseq;
        if (osq < _ackedTo) {
            osq = 256 + osq;
        }
        boolean timedout = false;
        for (int i = this._ackedTo; i <= this._oseq; i++) {
            int n = i % 256;
            FullFrame ff = _outbound[n];
            if (ff != null) {
                if (!ff.resendMe()) {
                    ff.log("outbound has max retries - timing out");
                    _outbound[n] = null;
                    timedout = true;
                    break;
                }
            }
        }
        if (timedout == true) {
            _timedout = true;
            removeSelf();
        }
    }

    /**
     * Returns if this call has timed out. A call times out when it
     * doesn't receive an ack on time.
     *
     * @return True if timed out, false if not timed out.
     */
    boolean isTimedout() {
        return _timedout;
    }

    /**
     * Passed a newly arrived frame. If the frame is the next one we
     * are expecting, then put it in the buffer and adjust our
     * expectations. Return it, so it can be acted upon. If it isn't
     * the next expected then ignore it and return null (Warn).
     *
     * @param ff FullFrame
     * @return FullFrame
     */
    synchronized FullFrame addIn(FullFrame ff) {
        FullFrame ret = null;
        int where = ff._oseq;
        int expected = this.getIseq();
        if (expected == where) {
            setIseq(++where);
            ret = ff;
        }
        return ret;
    }

    /**
     * Our NEW frame is acknowledged. Tell our Friend object.
     *
     * @param ack ProtocolControlFrame
     */
    void gotAckToNew(FullFrame ack) {
        if (this._rno == null) {
            _rno = new Character((char) (0xffff & ack._sCall));
        }
        Log.debug("Setting rno = " + (int) (_rno.charValue()));
        _peer.gotAckToNew(this);
    }

    /**
     * Sends a DTMF character
     *
     * @param c The DTMF character
     * @see DtmfFrame#DtmfFrame(Call, char)
     */
    public void sendDTMF(char c) {
        new DtmfFrame(this, c);
    }

    /**
     * Returns the status string
     *
     * @return The status
     */
    public String getStatus() {
        String ret = "Call " + (this._callIsInbound ? " from " : " to ")
                + this._farNo;
        ret += _answered ? " connected " : " in progress ";
        return ret;
    }

    /**
     * We hang up this call. Send a HANGUP to our peer.
     *
     * @see #hungup
     */
    public void hangup() {
        ProtocolControlFrame cal = new ProtocolControlFrame(this);
        cal.sendHangup();
    }

    /**
     * Received a new inbound call. Returns whether or not we accept it.
     *
     * @param callingNo   The peer's calling number
     * @param calledNo    The peer's called number
     * @param callingName The peer's calling name
     * @param username    The peer's username for authentication
     * @return True if we will accept, false if we reject
     */
    public Boolean newCallFrom(String callingNo, String calledNo,
                               String callingName,
                               String username) {
        Boolean ret = null;
        Log.debug("Call.newCallFrom: calledNo=" + calledNo
                + ", callingNo=" + callingNo
                + ", callingName=" + callingName
                + ", username=" + username);
        if (_farNo == null) {
            // first time we have seen this one.
            _farNo = callingNo;
            _farName = callingName;
            _nearNo = calledNo;
            this._callIsInbound = true;
            ret = _peer.willAccept(this);
            if (ret == true) {//edited by benaiad
                if (ringer == null || ringer.stop == true) {
                    ringer = new Ringer();
                    ringer.start();
                }
            }
        }
        return ret;
    }

    /**
     * We had an ack to our accept. Make our Friend object tell its gui.
     */
    void acceptedCall() {
        _peer.tellGuiNewCall(this);
    }

    /**
     * Returns if this call has been answered.
     *
     * @return True is answered, false if not
     */
    public boolean isAnswered() {
        return this._answered;
    }

    /**
     * Answers an incoming call.
     */
    public void answer() {
        ControlFrame ans = new ControlFrame(this);
        ans.sendAnswer();
        this.setAnswered(true);
    }

    public void hold() {

        ProtocolControlFrame quelch = new ProtocolControlFrame(this);
        quelch.sendQuelch();


        ControlFrame hold = new ControlFrame(this);
        hold.SendHold();
    }

    public void unhold() {

        ProtocolControlFrame unquelch = new ProtocolControlFrame(this);
        unquelch.sendUnQuelch();

        ControlFrame unhold = new ControlFrame(this);
        unhold.SendUnHold();
    }

    /**
     * Returns the far number.
     * <p/>
     * On an outgoing call this represents 'CALLED NUMBER' IE,
     * on an incoming call this represents 'CALLING NUMBER' IE.
     *
     * @return The far number
     */
    public String getFarNo() {
        return this._farNo;
    }

    /**
     * Returns the near number.
     * <p/>
     * On an outgoing call this represents 'CALLING NUMBER' IE,
     * on an incoming call this represents 'CALLED NUMBER' IE.
     *
     * @return The near number
     */
    public String getNearNo() {
        return this._nearNo;
    }

    /**
     * Returns the far name.
     * <p/>
     * On an incoming call this represents 'CALLING NAME' IE,
     * on an outgoing call will be null.
     *
     * @return The far name
     */
    public String getFarName() {
        return _farName;
    }

    /**
     * Returns the near name.
     * <p/>
     * On an outgoing call this represents 'CALLING NAME' IE,
     * on an incoming call this will be null.
     *
     * @return The near name
     */
    public String getNearName() {
        return _nearName;
    }

    /**
     * Make a ringing noice.
     */
    void setRinging() {
        if (!_recvdFirstAudioFrame) {
            _audio.startRinging();
//            if(ringer==null || ringer.stop==true){
//                ringer=new Ringer();
//                ringer.start();
//            }
        }
        _peer.gotRinging(this);
        Log.debug("going to make a remote ringing noise...!");
    }

    /**
     * Sets the accepted audio format. This bit of information comes in
     * with a received ACCEPT.
     *
     * @param format The audio format
     */
    void setAudioFormat(Integer format) {
        AudioInterface base = _peer.getAudioFace();
        _audio = base.getByFormat(format);
        if (_audio != null) {
            _format = _audio.getFormatBit();
            Log.debug("using audio format = " + _format);
        } else {
            Log.warn("cant set audio format " + format);
        }
    }

    /**
     * Returns the frame size. This is base on the audio sample size.
     *
     * @return the frame size
     */
    int getFrameSz() {
        int ret = 0;
        if (_audio != null) {
            ret = _audio.getSampSz();
        }
        return ret;
    }

    /**
     * getAudioFace
     *
     * @return AudioInterface
     */
    AudioInterface getAudioFace() {
        return _peer.getAudioFace();
    }
}
TOP

Related Classes of org.asteriskjava.iax.protocol.Call

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.