unit uSIPClient;

interface

uses
  Classes,GTSIPCTRLLib_TLB, Windows, ComObj, uIVRClient, SysUtils,syncobjs, ActiveX;


{
LinkSys SPA 2002 Configuration Changes

1) Allow Calling by IP Number
2) Change RTP Packet Size to .020  this prevented this error:
X [2008-05-19 21:17:27] Failed to put a frame(480 bytes) into a jitbuf which is limited on max 320 bytes!
X [2008-05-19 21:17:27] RTP packet arrived! but lost!
3) Preferred Codec G711u
4) Set DTMF TX Method to "INFO"
5) Set DTMF TX Mode to "NORMAL"
}



type
  TSIP = class(TThread)
  private
    { Private declarations }
     StartupDir: String;
     ChannelStatus: array[0..32] of ShortString;
     CallCount: array[0..32] of Integer;
     procedure WriteLogs(s: string);
  protected
    procedure Execute; override;
     procedure SIPAudioPlayDone(Sender: TObject; ChanID,
      DoneReason: Integer; const DTMFStr: WideString);
    procedure SIPAudioRecordDone(Sender: TObject; ChanID,
      DoneReason: Integer; const DTMFStr: WideString);
    procedure SIPAudioStatus(Sender: TObject; ChanID, ResType,
      StatusCode: Integer);
    procedure SIPCallConnected(Sender: TObject; ChanID: Integer);
    procedure SIPCallDialing(Sender: TObject; ChanID: Integer;
      const Caller, Callee: WideString);
    procedure SIPCallHolding(Sender: TObject; ChanID,
      HoldOnStatus: Integer);
    procedure SIPCallIdle(Sender: TObject; ChanID: Integer);
    procedure SIPCallOffered(Sender: TObject; ChanID: Integer;
      const Caller, Callee, DestAddr, ViaAddr, IPAddr: WideString;
      IPPort: Integer);
    procedure SIPCallRinging(Sender: TObject; ChanID: Integer);
    procedure SIPCallTransfering(Sender: TObject; ChanID: Integer;
      const DestAddr: WideString);
    procedure SIPDTMFDone(Sender: TObject; ChanID,
      DoneReason: Integer; const DTMFStr: WideString);
    procedure SIPDTMFKeyDown(Sender: TObject; ChanID: Integer;
      KeyValue: Smallint; ClockTicks: Integer);
    procedure SIPDTMFKeyUp(Sender: TObject; ChanID: Integer;
      KeyValue: Smallint; ClockTicks: Integer);
    procedure SIPNotifySimpleMsgSummary(Sender: TObject;
      const MsgWaiting, MsgAccount, VoiceMsg: WideString);
    procedure SIPRecvError(Sender: TObject; ChanID,
      ErrCode: Integer);
    procedure SIPRecvRegStatus(Sender: TObject; SIPAccID, Status,
      RegUnixTime: Integer);
    procedure SIPRecvStatus(Sender: TObject; ChanID1, ChanID2,
      ChanStatus: Integer);
    procedure SIPRecvTimer(Sender: TObject; ChanID: Integer);
  public
    DTMFMethod: string;
    SIP: TGTSIPAPI;
    IVRThread: array[0..32] of TIVR;
    LockSIP: TCriticalSection;
    WriteErrors: Integer;
    Channels: Integer;
    Logging: Boolean;
    procedure ProcessIVRS;
    procedure ProcessIVR(chan: Integer);
    procedure Log(chan: Integer; s: string);
    function GetTaskName(Task: TTask): String;
    function GetChannelStatus(chan: Integer): string;
    procedure ErrorLog(chan: Integer; s: string);
    procedure LogX(chan: Integer; s: string);
    function GetIVRThread(ChanID: Integer):TIVR;
  end;

implementation

{ Important: Methods and properties of objects in VCL can only be used in a
  method called using Synchronize, for example,

      Synchronize(UpdateCaption);

  and UpdateCaption could look like,

    procedure TSIP.UpdateCaption;
    begin
      Form1.Caption := 'Updated in a thread';
    end; }

{ TSIP }

procedure TSIP.Execute;
var
s: string;
i: Integer;
begin
  { Place thread code here }

    WriteLogs('Begin Executing');
    WriteErrors := 0;

    StartupDir:= GetCurrentDir;
    CoInitialize(nil);
    SIP:= TGTSIPAPI.Create(Nil);
    Log(0,'TGTSIPAPI Created');

    SIP.OnCallIdle:= SIPCallIdle;
    SIP.OnCallConnected:= SIPCallConnected;
    SIP.OnRecvTimer:= SIPRecvTimer;
    SIP.OnDTMFKeyDown:= SIPDTMFKeyDown;
    SIP.OnDTMFKeyUp:= SIPDTMFKeyUp;
    SIP.OnDTMFDone:= SIPDTMFDone;
    SIP.OnCallOffered:= SIPCallOffered;
    SIP.OnCallConnected:= SIPCallConnected;
    SIP.OnAudioPlayDone:= SIPAudioPlayDone;
    SIP.OnAudioRecordDone:= SIPAudioRecordDone;
    SIP.OnRecvError:= SIPRecvError;
    SIP.CreateEnv;


    SIP.CFGSetValue('gtsrv.sip.server.model', '1');

   //if use local NAT address in SIP message
   //Set it to 1 if you are using Asterisk or 3CX as local registration server
    SIP.CFGSetValue('gtsrv.sip.use.nat.addr', '1');

	//SIP Port, default 5060
	SIP.CFGSetValue('gtsrv.sip.ip.port', '5059');

	//RTP PORT
	SIP.CFGSetValue('gtsrv.sip.rtpstartrange', '22400');
	SIP.CFGSetValue('gtsrv.sip.rtpendrange', '22800');

    //Log
	SIP.CFGSetValue('gtsrv.log.level', '4');
    SIP.CFGSetValue('gtsrv.log.filename', StartupDir + '\gtclient.log');

	//channnel numbers
	SIP.CFGSetValue('gtsrv.sip.boardnum.per.server', '4');
	SIP.CFGSetValue('gtsrv.sip.spannum.per.board', '1');
	SIP.CFGSetValue('gtsrv.sip.channum.per.span', IntToStr(Channels div 4));
	
//We don’t recommend you set "gtsrv.sip.channum.per.span" more than 16.
//Sugguested Configuration of CHANNELS, SPANS, and Boards:
//Channels	gtsrv.sip.channum.per.span	gtsrv.sip.spannum.per.board	gtsrv.sip.boardnum.per.server
//4			4				1				1
//8			8				1				1
//16			8				2				1
//24			8				3				1
//32			8				4				1
//40			8				5				1
//64			8				8				1
//128			8				8				2
//256			8				8				4
//512			16				8				4	

    //Internal communication port
	SIP.CFGSetValue('gtsrv.net.port', '8814');


    // Preferred codecs
    //Prefered audio codecs
    //18 = G729a/b, not support yet
    //102 = Speex
    //101 = iLBC 30ms
    //100 = iLBC 20ms
    //3 = GSM
    //98 = G726-32
    //0 = MULAW
    //8 = ALAW

    SIP.CFGSetValue('gtsrv.sip.prefered.codec','0,3,98,8');

    //"gtsrv.sip.dtmf.method"
    // 0 = default, Using DTMF in audio
    // 1 = SIP Info
    // 2 = RTP Package
    // 3 = Auto(0 or 2)

    SIP.CFGSetValue('gtsrv.sip.dtmf.method',DTMFMethod);

	//Application name, it is related to sdk licence
	//Please contact admin@pcbest.net to get licence info
	//SIP.SetAppName('YourAppName');
    //SIP.CFGSetValue('gtsrv.licence.key', '');

	//If StartServer returns false, please make sure SIP port is not being used by another application!
    SIP.StartServer;
	
    SIP.SendGetRegStatus(0);

    Log(0, 'SIP DTMFMethod: ' + DTMFMethod);
    Log(0, 'SIP Channels: ' + IntToStr(SIP.GetTotalChannelNumber));
    Log(0, 'SIP Licensed To [' + SIP.GetLicTo + ']');

    While(Terminated = False) do
    begin
        Sleep(100);
        ProcessIVRs;
        SIP.ProcessGTAPIEvent;

        for i:= 0 to Channels -1 do begin
            if (IVRThread[i].WaitingTask = SIPCall) and IVRThread[i].Suspended then begin
                CallCount[i] := CallCount[i] + 1;
                if CallCount[i] > 600 then begin
                    s := '<sip:' + IVRThread[i].AuthorID + '@'+  IVRThread[i].SIPAddr + '>';
                    Log(i, 'REDIAL SIP:' +  s);
                    CallCount[i]:= 0;
                    SIP.SendMake(i, s, '')
                end;
            end;
        end;
    end;

    SIP.StopServer;
    SIP.DestroyEnv;
    CoUninitialize;
end;

procedure TSIP.SIPAudioPlayDone(Sender: TObject; ChanID,
  DoneReason: Integer; const DTMFStr: WideString);
var
t: TIVR;
begin
    t:= GetIVRThread(ChanID);
    Log(ChanID, 'SIPAudioPlayDone: ' +  GetTaskName(t.WaitingTask));
    if t.WaitingTask= SIPPLay then
    begin
       t.WaitingTask:= SIPNone;
       if t.Suspended then begin
            Log(ChanID, 'SIPAudioPlayDone: Resume ');
            t.Suspended := False;
       end else begin
         Log(ChanID, 'SIPAudioPlayDone: Thread not Suspended ');
       end;
    end else begin
       Log(ChanID, 'SIPAudioPlayDone: ' +  GetTaskName(t.WaitingTask) + '<> SIPPLay');
    end;
end;

procedure TSIP.SIPAudioRecordDone(Sender: TObject; ChanID,
  DoneReason: Integer; const DTMFStr: WideString);
var
t: TIVR;
begin
    t:= GetIVRThread(ChanID);
//
    Log(ChanID, 'SIPAudioRecordDone: ' +  GetTaskName(t.WaitingTask));
    if t.WaitingTask= SIPRecord then
    begin
       t.WaitingTask:= SIPNone;
       if t.Suspended then
            t.Suspended:= False;
    end;

end;

procedure TSIP.SIPAudioStatus(Sender: TObject; ChanID, ResType,
  StatusCode: Integer);
begin
//
end;

procedure TSIP.SIPCallConnected(Sender: TObject; ChanID: Integer);
var
t: TIVR;
begin
    t:= GetIVRThread(ChanID);
    if t.WaitingTask= SIPConnect then begin
        Log(ChanID, 'SIPCallConnected: ' +  GetTaskName(t.WaitingTask));
        SIP.SendEnableDTMF(ChanID);
        t.WaitingTask:= SIPNone;
        t.Connected:= True;
    end else if t.WaitingTask= SIPCall then begin
        Log(ChanID, 'SIPCallConnected: ' +  GetTaskName(t.WaitingTask));
        SIP.SendEnableDTMF(ChanID);
        t.WaitingTask:= SIPNone;
        t.Connected:= True;
    end else begin
        Log(ChanID, 'ERROR! SIPCallConnected during: ' +  GetTaskName(IVRThread[ChanID].WaitingTask));
        //t.Connected:= False;
    end;
    if t.Suspended then
        t.Suspended:= False;
end;

procedure TSIP.SIPCallDialing(Sender: TObject; ChanID: Integer;
  const Caller, Callee: WideString);
begin
//
end;

procedure TSIP.SIPCallHolding(Sender: TObject; ChanID,
  HoldOnStatus: Integer);
begin
//
end;

procedure TSIP.SIPCallIdle(Sender: TObject; ChanID: Integer);
var
t: TIVR;
begin
    t:= GetIVRThread(ChanID);
    Log(ChanID, 'SIPCallIdle: ' +  GetTaskName(t.WaitingTask));
    t.WaitingTask:= SIPNone;
    t.Task:= SIPNone;
    t.Connected:= False;
    if t.Suspended then
        t.Suspended:= False;
end;

procedure TSIP.SIPCallOffered(Sender: TObject; ChanID: Integer;
  const Caller, Callee, DestAddr, ViaAddr, IPAddr: WideString;
  IPPort: Integer);
var
t: TIVR;
begin
    t:= GetIVRThread(ChanID);
     if t.WaitingTask= SIPConnect then begin
        Log(ChanID, 'Answering Call');
        Log(ChanID, 'Caller  : ' +  Caller);
        Log(ChanID, 'Callee  : ' +  Callee);
        Log(ChanID, 'DestAddr: ' +  DestAddr);
        Log(ChanID, 'ViaAddr: '  +  ViaAddr);
        Log(ChanID, 'IPAddr: '   +  IPAddr+ ':' + IntToStr(IPPort) );
        SIP.SendAnswer(ChanID);
     end else begin
        Log(ChanID, 'SIPCallOffered: ' +  GetTaskName(t.WaitingTask));
        Log(ChanID, 'ERROR - REFUSING to Answer Call: Another call already in progress');
        t.Connected:= False;
        if t.Suspended then
            t.Suspended:= False;
     end;
end;

procedure TSIP.SIPCallRinging(Sender: TObject; ChanID: Integer);
begin
//
end;

procedure TSIP.SIPCallTransfering(Sender: TObject; ChanID: Integer;
  const DestAddr: WideString);
begin
//
end;

procedure TSIP.SIPDTMFDone(Sender: TObject; ChanID,
  DoneReason: Integer; const DTMFStr: WideString);
begin
//
   //Log(ChanID, 'SIPDTMFDone: ' +  GetTaskName(IVRThread[ChanID].WaitingTask));
   //Log(ChanID, 'SIPDTMFDone:      Key = ' + DTMFStr);
end;

procedure TSIP.SIPDTMFKeyDown(Sender: TObject; ChanID: Integer;
  KeyValue: Smallint; ClockTicks: Integer);
var
t: TIVR;
begin

    t:= GetIVRThread(ChanID);

    Log(ChanID, 'SIPDTMFKeyDown: ' +  GetTaskName(t.WaitingTask));
    Log(ChanID, 'SIPDTMFKeyDown:      Key = ' + Char(KeyValue) );

    t.AddDigit(Char(KeyValue));
    if t.WaitingTask= SIPDigits then begin
        t.WaitingTask:= SIPNone;
        if t.Suspended then
            t.Suspended:= False;
    end;

    if t.WaitingTask= SIPRecord then
        SIP.SendStopAudio(ChanID);
    if t.WaitingTask= SIPPlay then
        SIP.SendStopAudio(ChanID);
end;

procedure TSIP.SIPDTMFKeyUp(Sender: TObject; ChanID: Integer;
  KeyValue: Smallint; ClockTicks: Integer);
begin
    //Log(ChanID, 'SIPDTMFKeyUp: ' +  GetTaskName(IVRThread[ChanID].WaitingTask));
    //Log(ChanID, 'SIPDTMFKeyUp:      Key = ' + Char(KeyValue) );
end;

procedure TSIP.SIPNotifySimpleMsgSummary(Sender: TObject;
  const MsgWaiting, MsgAccount, VoiceMsg: WideString);
begin
//
end;

procedure TSIP.SIPRecvError(Sender: TObject; ChanID,
  ErrCode: Integer);
begin
//
      //ErrorLog(chan,  GetErrorMessage(ErrCode));
end;

procedure TSIP.SIPRecvRegStatus(Sender: TObject; SIPAccID, Status,
  RegUnixTime: Integer);
begin
//
end;

procedure TSIP.SIPRecvStatus(Sender: TObject; ChanID1, ChanID2,
  ChanStatus: Integer);
begin
//
end;

procedure TSIP.SIPRecvTimer(Sender: TObject; ChanID: Integer);
begin
//


end;

procedure TSIP.ProcessIVRS;
var
i: Integer;
t: TIVR;
begin
//
    for i:= 0 to Channels -1 do
    begin
         t := IVRThread[i];
         if t <> Nil then begin
             if t.Suspended then begin
                 if t.Task <> SIPNone then begin
                    ProcessIVR(i);
                 end;
             end;
         end;
    end;
end;

procedure TSIP.ProcessIVR(chan: Integer);
var
Task: TTask;
FilePath: string;
s: string;
t: TIVR;
begin

    t:= GetIVRThread(chan);

    Task:= t.Task;
    Log(chan, 'Enter ProcessIVR:' +  GetTaskName(Task));
    t.task:= SIPNone;
    t.WaitingTask:= Task;

    Case Task of
        SIPPlay: begin
            SIP.SendStopAudio(chan);
            FilePath := StartupDir + '\Prompts\' + t.PlayFileName;
            SIP.SendPlayAudio(chan,FilePath ,0, '',0);
            Log(chan, 'PlayFileName:' +  t.PlayFileName);
        end;
        SIPRecord: Begin
            SIP.SendStopAudio(chan);
            FilePath := StartupDir + '\Prompts\' + t.RecordFileName;
            SIP.SendRecordAudio(chan, FilePath, 0, '',0);
            Log(chan, 'RecordFileName:' +  t.RecordFileName);
        end;
        SIPConnect: Begin

        end;
        SIPDisconnect: Begin
            SIP.SendHungUp(chan);
        end;
        SIPDigits: Begin

        end;
        SIPCall: begin
             s := '<sip:' + IVRThread[chan].AuthorID + '@'+  IVRThread[chan].SIPAddr + '>';
             Log(chan, 'Call SIP:' +  s);
             CallCount[chan]:= 0;
             SIP.SendMake(chan, s, '')
        end;
        SIPSendDTMF: begin
            Log(chan, 'SIPSendDTMF:' +  t.DTMFDigit);
            SIP.SendPlayDTMFStr(chan, t.DTMFDigit);
            t.WaitingTask:= SIPNone;
            if t.Suspended then
                t.Suspended:= False;
        end;
    end;
    Log(chan, 'Exit ProcessIVR:' +  GetTaskName(Task));
end;

procedure TSIP.ErrorLog(chan: Integer; s: string);
begin
   //
end;

procedure TSIP.Log(chan: Integer; s: string);
begin
    try
        //LockSIP.acquire;
        if Not Logging then exit;
        LogX(chan, s);
    finally
        //LockSIP.release;
    end;
end;

procedure TSIP.LogX(chan: Integer; s: string);

var
    Filename: string;
    LogFile: TextFile;
begin

    try
        Filename := StartupDir + '\SIPCLIENT' + IntToStr(chan) + ' .log';
        AssignFile(LogFile, Filename);
        if FileExists(Filename) then
            Append(LogFile)
        else
            Rewrite(LogFile);
        s:= IntToStr(WriteErrors) + ':' + DateTimeToStr(Now) + ':SIP:' + s;

        ChannelStatus[chan] := s;
        Writeln(LogFile, s);
    except
         WriteErrors := WriteErrors + 1;
    end;

    try
        CloseFile(LogFile);
    except

    end;
end;
function  TSIP.GetChannelStatus(chan: Integer): string;
begin
    Result := '???';
    try
        //LockSIP.acquire;
        if (chan >= 0) and (chan < Channels) then
            Result := ChannelStatus[chan];
    finally
        //LockSIP.release;
    end;
end;


function TSIP.GetTaskName(Task: TTask): String;
begin

    Result:= 'Unknown';

    Case Task of
        SIPPlay: Result:= 'SIPPlay';
        SIPRecord: Result:= 'SIPRecord';
        SIPConnect: Result:= 'SIPConnect';
        SIPDisconnect: Result:= 'SIPDisconnect';
        SIPDigits: Result:= 'SIPDigits';
        SIPNone: Result:= 'SIPNone';
        SIPCall: Result:= 'SIPCall';
        SIPSendDTMF: Result:= 'SIPSendDTMF';
    end;
end;
procedure TSIP.WriteLogs(s: string);
var
i: Integer;
begin
    //LockSIP.Acquire;
    try
        for i:= 0 to Channels -1 do
        begin
            Log(i, s);
        end;
    finally
      //  LockSIP.release
    end;
end;
function TSIP.GetIVRThread(ChanID: Integer):TIVR;
begin
    Result:= Nil;
    try
       // LockSIP.Acquire;
        if (ChanID >= 0) and (ChanID <= Channels) then
            Result := IVRThread[ChanID];
    finally
        //LockSIP.release
    end;

end;

end.
