This page is an explanation article for the source code of MediaGateway SIP Example for Ozeki VoIP SIP SDK to provide an overall view. Please find the source code parts and their explanation below this page.

Server implementation

In this sample program, the server is responsible for keeping connection with the SIP PBX, and also for setting up/accepting calls, hanging up calls, forwarding media data between the PBX/SIP device and the Silverlight client and distributing accounts that are required for logging into the PBX. The server also makes these accounts logged into the PBX for the Silverlight clients.

These tasks will be realized by the SIPGateway class in order to exploit the functions provided by Ozeki VoIP SIP SDK. It has been derived from the MediaGateway class. Furthermore, in order to implement SIP functionality quickly and easily, it uses the tools ensured by Ozeki VoIP SIP SDK.

SIPGateway Constructor

A SIPAccountPool is built in the class constructor that includes a certain number of accounts that belongs to a SIP PBX (in this example, it is Asterisk PBX). All connected clients are demonstrated by a SIPClient object, they are recorded in a ConcurrentDictionary. In order to exploit the implementation of the easy-to-handle, two-way communication between the client and the server, the stream service is got with the MediaGateway GetService<IStreamService>(); command (Code 1).

public SIPGateway()
        {
            sipClients = new ConcurrentDictionary<IClient, SIPClient>();
            softPhone = SoftPhoneFactory.CreateSoftPhone("192.168.113.6", 5060, 5800, 5060);

            sipAccountPool = new SIPAccountPool();

            for (int i = 80; i < 99; i++)
            {
                var name = "oz8" + i;
                var sipAccount = new SIPAccount(true, name, name, name, name, "192.168.115.1", 5060);

                sipAccountManager.AddSIPAcount(sipAccount);
            }

            streamService = GetService<IStreamService>();
        }
Code 1 - The server class constructor method

The softphone object that can be seen in the code is provided by the Ozeki VoIP SIP SDK. It represents a telephone with full functionality. Based on the SDK licensing, it is possible to add even more telephone lines through which calls are handled. The softphone can be created with the use of SoftPhoneFactory.CreateSoftPhone method call, in this example, the IP address and port range of Asterisk PBX is also used.

SipGateWay Public methods

OnClientConnect is an overridden method (Code 2). It can originally be found in SIPGateway class. Via this method the VoIP SIP SDK notifies if a client tries to connect.

public override void OnClientConnect(IClient client, object[] parameters)
        {
            Console.WriteLine("New client connected");

            var sipAccount = sipAccountPool.GetSIPAccount();

            if(sipAccount == null)
            {
                client.InvokeMethod("OnRegistrationStateChanged", RegistrationState.ConnectionLimit);
                client.Close();
                return;
            }

            var sipClient = new SIPClient(client, streamService, softPhone, sipAccount);
            sipClients.TryAdd(client, sipClient);

            sipClient.Register();
        }
Code 2 - This method is invoked when a client requests connection to the server

If there is a free SIP account in the SipAccountPool for the client that tries to connect, then the SipClient object of the client is created with this account and the necessary parameters. In case there is no free account, the client that tries to connect will be notified and the connection is closed.

OnClientDisconnect method (Code 3) is only responsible for removing the connected client's reference from ConcurrentDictionary and for releasing the SIP account that has been used by the client and for unregistering the account from the PBX.

public override void OnClientDisconnect(IClient client)
        {
            Console.WriteLine("Client disconnected");

            SIPClient sipClient;
            if (sipClients.TryRemove(client, out sipClient))
            {
                sipClient.Unregister();
                sipAccountPool.AddSIPAcount(sipClient.Account);
            }
        }
Code 3 - This method handles client disconnection from the server

OnSteamPublishStart (Code 4) will be invoked when the given client published its MediaStreamSender object, this way, it will be subscribed its stream MediaDataReceived event for the MediaDataReceived event handler of the SIPClient. This will forward the received data to the proper direction. The code implements these issues as follows:

public override void OnStreamPublishStart(IClient client, IMediaStream mediaStream)
        {
            Console.WriteLine("Media stream published. Stream name {0}", mediaStream.Name);

            SIPClient sipClient;
            if (sipClients.TryGetValue(client, out sipClient))
                mediaStream.MediaDataReceived += sipClient.MediaDataReceived;
        }
Code 4 - The OnStreamPublishStart method

OnStreamClose (Code 5) makes the opposite of OnSteamPublishStart. OnStreamClose finishes the data transfer of the published stream by unsubscribing the event handler of the previously subscribed event.

public override void OnStreamClose(IClient client, IMediaStream mediaStream)
        {
            Console.WriteLine("Media stream closed.");

            SIPClient sipClient;
            if (sipClients.TryGetValue(client, out sipClient))
                mediaStream.MediaDataReceived -= sipClient.MediaDataReceived;
        }
Code 5 - This method invokes when the clients stop streaming

The described methods above can be considered as the basic methods of Ozeki VoIP SIP SDK. They have been overridden according to the current implementation in order to provide them the appropriate functionality.

The next 3 methods have the following functionality: Call initiation, HangUP - rejecting incoming calls/hangup active call - and the Accept call functionality.

These methods extend the built-in services of Ozeki VoIP SIP SDK. They can be invoked via the mechanisms of the SDK that are related to these functions. This is the follows: the InvokeOnConnection method of the created MediaConnection in the client (Code 6). For example, in the following way in case of Call:

mediaConnection.InvokeOnConnection("Call", dialNumber);
Code 6 - This instruction invokes the Call method from the client-side

That will result the following method call on the server side (Code 7).

public void Call(IClient client, string phoneNumber)
        {
            SIPClient sipClient;
            if (sipClients.TryGetValue(client, out sipClient))
                sipClient.Call(phoneNumber);
        }
Code 7 - The Call method on the server-side

This will forward the request to the SIPClient class. Hangup and Accept methods work similarly to the Call method. This way it is not detailed here, but it can be checked in the source code available for download on this webpage.

The SIPClient class for representing the clients on the server-side

A server-side SIPClient object will be created for each connected client and handle the status of the phone line belonging to the client.

SIPClient constructor (Code 8) gets its client's reference, server stream service's reference, the softphone that is responsible for representing the telephone and an account derived from a SIPAccountPool in its constructor.

public SIPClient(IClient client, IStreamService streamService, ISoftPhone softPhone, SIPAccount account)
        {
            mediaConnector = new MediaConnector();
            myMediaSender = new MyMediaSender();
            phoneCallMediaSender = new PhoneCallMediaSender(VoIPMediaType.Audio);
            codecConverter = CodecConverter.Instance;

            mediaConnector.Connect(myMediaSender, phoneCallMediaSender);

            this.streamService = streamService;
            this.softPhone = softPhone;
            this.client = client;

            Account = account;
        }
Code 8 - The SIPClient class constructor

The class has a Mediaconnector object that is a tool provided by Ozeki VoIP SIP SDK. It is responsible for connecting the media data receiving from the client to the PhoneCallMediaSender object provided by the Ozeki VoIP SIP SDK. The media data that is received from the client is matched with the mentioned PhoneCallMediaSender via a MyMediaSender wrapper class with the following command shown in Code 9.

mediaConnector.Connect(myMediaSender, phoneCallMediaSender);
Code 9 - Connection establishment

Register() method (Code 10) will create the phone line that belongs to the client and it will also subscribe to the necessary events.

public virtual void Register()
        {
            phoneLine = softPhone.CreatePhoneLine(Account);

            if (phoneLine.RegisteredInfo == PhoneLineState.RegistrationSucceded)
               OnPhoneLineStateChanged(phoneLine.RegisteredInfo);

            phoneLine.PhoneLineStateChanged += PhoneLine_PhoneLineInformationCanged;
            softPhone.IncommingCall += SoftPhone_IncommingCall;


            softPhone.RegisterPhoneLine(phoneLine);
        }
Code 10 - Registering to the SIP PBX

OnPhoneLineStateChanged event is responsible for indicating the changes in the telephone line status. The IncommingCall event of Softphone is for indicating incoming calls. The Unregister() method unregisters the phoneline and unsubscribes from the previously subscribed events. Call(string dialNumber) will dial the telephone number provided in the parameter via the Call object provided by Ozeki VoIP SIP SDK, furthermore, it subscribes to the events of this object that are necessary for making the call (Code 11).

public virtual void Call(string dialNumber)
        {
            if (phoneLine == null)
            {
                System.Diagnostics.Debug.Fail("PhoneLine null");
                return;
            }

            if ((phoneLine.RegisteredInfo == PhoneLineState.RegistrationSucceded || phoneLine.RegisteredInfo == PhoneLineState.NoRegNeeded) && call == null)
            {
                call = softPhone.CreateCallObject(phoneLine, dialNumber);

                call.CallErrorOccured += Call_ErrorOccured;
                call.CallStateChanged += Call_StateChanged;
                call.MediaDataReceived += Call_MediaDataReceived;
                call.Start();
            }
        }
Code 11 - The setup of the call object

The Accept() method accepts the request for an incoming call, while the HangUp() method rejects the incoming call or hangs up active calls.

The Call_StateChanged event handler is for indicating call status during call setup and the call progress. It helps identify in which status the call is (Busy, Cancelled, Ringing, Incall, Completed...). InCall status is handled here. InCall status shows that point of the call when the media data sending and receiving can be started.

protected virtual void Call_StateChanged(object sender, VoIPEventArgs<CallState> e)
        {
            switch (e.Item)
            {
                case CallState.InCall:
                  phoneCallAudioSender.AttachToCall(call);
                    phoneCallAudioReceiver.AttachToCall(call);

                    publishStreamName = Guid.NewGuid().ToString(); // Ez az amit a kliens publik�l
                    playStreamName = Guid.NewGuid().ToString(); // Ez az amit a szerver publik�l

                    var mediaStream = streamService.CreateStream(playStreamName);
                    mediaGatewayAudioReceiver = new MediaGatewayAudioReceiver(mediaStream);

                    mediaConnector.Connect(phoneCallAudioReceiver, mediaGatewayAudioReceiver);

                    OnInCall(publishStreamName, playStreamName);
                    break;

                case CallState.Completed:
                    phoneCallAudioSender.Detach();
                    phoneCallAudioReceiver.Detach();

                    streamService.RemoveStream(mediaGatewayAudioReceiver.MediaStream);
                    mediaConnector.Disconnect(phoneCallAudioReceiver, mediaGatewayAudioReceiver);
                    mediaGatewayAudioReceiver.Dispose();

                    mediaGatewayAudioReceiver = null;
                    call = null;
                    break;
            }
            OnCallStateChanged(e.Item);
        }

Code 12 - The EventHandler method for the call state changing

Two unique stream identifiers are created for the client in InCall status. One ID is for sending voice data, the other ID is for receiving voice data from the remote end. This will be indicated for the client via OnInCall call. Callstate.Completed status indicates that the actual call has been completed, this way, the created streams will be removed from the service and the call. The method notifies the client in its last line.

The connection between the Mediagateway and the SIP SDK is made by two MediaHandler objects (Code 14). These objects are connected to the PhoneCallAudioSender and PhoneCallAudioReceiver in the Call_StateChanged method.

MediaGatewayAudioReceiver mediaGatewayAudioReceiver;
MediaGatewayAudioSender mediaGatewayAudioSender;
Code 13 - The MediaHandlers that are used for connecting the MediaGateway and the SIP tools

Two-way communication is built up between the server and the client, so the server can invoke certain methods of the client. It is similar to the case when the client can invoke the server's extended service functions. It differs only in that the InvokMethod of the IClient object is invoked in this case (Code 14).

void OnIncomingCall(string phoneNumber)
        {
            client.InvokeMethod("OnIncomingCall", phoneNumber);
        }
        
Code 14 - The server-side method call for invoking client-side methods

Where the server indicates to the client that there is an incoming call from the phone number that has been specified in the parameter. The InvokeMethod is the follows shown in Code 15.

public void InvokeMethod(string methoName, params object[] parameters)
Code 15 - The definition of method invocation method

The SIPClient also sends call status changes to the client similarly to this method via OnCallStateChanged and OnCallErrorOccured methods.

Client implementation

The Silverlight client demonstrates a one-line webphone with making/accepting/hanging up calls and DTMF handling functionality.

User Interface

The graphical user interface is built up using 3 main GUI elements: NumPad, Display and MainPage. The Display panel only displays the actual status of the application with using Silverlight Binding Engine. The NumPad includes the necessary buttons of the phone, and sends the functionality of these buttons to the MainPage GUI element. The MainPage is responsible for transferring commands arriving from NumPad to the logic that is described by SIPClient in this case. Besides this, the MainPage also published the changes occurred in logic (so the data displayed on Display). After loading the interface, controls are created for recording and playing the audio for the SIPClient object. Then it is necessary to subscribe to the events that display certain information. Then it needs to be connected to the server (Code 16).

void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            phoneState = PhoneState.Normal;
            numpad.owner = this;
            display.DataContext = this;
            UserName = "unregistered";

            microphone = Microphone.GetMicrophone();
            audioPlayer = new AudioPlayer();

            sipClient = new SIPClient(microphone, audioPlayer);

            CallInfo = "Registering...";

            sipClient.ConnectionStateChanged += sipClient_ConnectionStateChanged;
            sipClient.RegistrationStateChanged += sipClient_RegistrationStateChanged;
            sipClient.PhoneLineStateChanged += sipClient_PhoneLineStateChanged;
            sipClient.CallStateChanged += sipClient_CallStateChanged;
            sipClient.CallErrorOccurred += sipClient_CallErrorOccurred;
            sipClient.SIPUserNameReceived += sipClient_SIPUserNameReceived;
            sipClient.IncomingCall += sipClient_IncomingCall;
                        sipClient.DTMFReceived += sipClient_DTMFReceived;
            sipClient.Connect();

        }
Code 16 - Initialization of the MainPage object
The client-side SIPClient class

The SIPClient is responsible for providing an interface to the server for the two-way client-server communication. This interface can be created with the use of the MediaConnection class of Ozeki MediaGateway SLCLient SDK easily. An object needs to be created with the given server's IP address, then the Connect method is invited with which it is connected to the server that has the IP address previously defined in constructor (Code 17).

public SIPClient(Microphone microphone, AudioPlayer audioPlayer)
        {
            this.microphone = microphone;
            this.audioPlayer = audioPlayer;

            mediaConnection = new MediaConnection("localhost:4502/SIPGateway");

            mediaConnection.Client = this;
            mediaConnection.ConnectionStateChanged += mediaConnection_ConnectionStateChanged;
        }
Code 17 - The SIPClient constructor on the client-side

A Microphone and Audioplayer objects are provided in the SIPClient constructor by the VoIP SIP SDK in order to make audio recording and playing functions of Silverlight version 4 easier.

Subscription to ConnectionStateChanged event in the constructor ensures a notification about the results of the connection to the server. To allow the server to call the public methods of the SIPClient object, it needs to be specified for the Client Property of the MediaConnection class in which SIPClient object it needs to search for the method (that has been invited by the server) in this case. (mediaConnection.Client = this;)

Since the communication logic related to SIP can be found in the server-side solution, this class is only responsible for forwarding data to the GUI and the server and displaying the data received from the server on the GUI. Of course, media data is an exception because it will be provided by the Microphone class and it will be attached to a MediaStreamSender object as an input device as the phone call is setup. It needs to be mentioned that besides attaching there are no further tasks because the appropriate audio compression is made by the Microphone object. Data arriving from the server are also excpeptions because they will be played by the AudioPlayer object that is attached to the MediaStreamReceiver.

The SIPClient ensures simple telephone functionality with the following methods: Call, HangUp, Accept.

  public void Call(string dialNumber)
        {
            mediaConnection.InvokeOnConnection("Call", dialNumber);
        }
Code 18 - The Call client-side method that invokes the server-side Call method

The Call method (Code 19) dials the telephone number that has been specified in parameter on the server side. Since the server side Call is not the part of the basic server side methods of Ozeki VoIP SIP SDK, it will be invited via the InvokeOnConnection mechanism of the MediaConnection (Code 19).

MediaConnection.IvnvokeOnConnection(string methodname, params object[] parameters )
Code 19 - The client-side method invokation instruction for calling server-side methods

HangUp rejects incoming calls or hangs up actual calls. Accept accepts incoming call requests and it reaches the necessary method of the server side via the InvokOnConnection similarly to Call.

OnCallStateChanged is a further method that is invoked from the server-side (Code 20). It displays the changes of the actual call status on the interface.

public void OnCallStateChanged(CallState callState)
        {
            if (callState == CallState.Completed)
                CloseStreams();

            var handler = CallStateChanged;

            if(handler != null)
                handler(this, new GenericEventArgs<CallState>(callState));
        }
        
Code 20 - The method invoked from server-side when tha call state changes

OnCallErrorOccurred, OnRegistrationStateChanged, OnPhoneLineStateChanged, OnRegisteredUserNameReceived methods have similar tasks as the above mentioned methods, so they are not detailed here.

The OnInCall method (Code 21) is responsible for notifying the client when the actual call has been accepted by the remote end; and the client should start playing the media data and should send the outgoing media data (that arrives from the microphone) to the server.

public void OnInCall(string publisStreamName, string playStreamName)
        {
            mediaStreamSender = new MediaStreamSender(mediaConnection);
            mediaStreamSender.AttachMicrophone(microphone);
            mediaStreamSender.Publish(publisStreamName);

            mediaStreamReceiver = new MediaStreamReceiver(mediaConnection);
            mediaStreamReceiver.AttachAudioPlayer(audioPlayer);
            mediaStreamReceiver.Play(playStreamName);
        }
Code 21 - The method that is invoked from the server-side in case of an incoming call

Based on the above mentioned it is done as follows: a MediaStreamSender object is created for sending the incoming voice data (that arrive from the microphone). The Microphone object (that is provided by Ozeki VoIP SIP SDK) has been attached to this created MediaStreamSender object. This way the server starts playing the outgoing media stream to the remote end. It then needs to be published with a unique name that will be received by the remote end via its MediaStreamReciver object after it has invited the Play method with the unique ID (that has been published by the given client). In this case, both stream identifiers are generated by the server and then it forwards the IDs to the clients, as it can be seen from the source code above.

The voice data that arrive to the MediaStreamReceiver object is handled by the Audioplayer provided by Ozeki VoIP SIP SDK. It will play the received voice data via the speakers of the computer (Code 22).

  mediaStreamReceiver = new MediaStreamReceiver(mediaConnection);
            mediaStreamReceiver.AttachAudioPlayer(audioPlayer);
            mediaStreamReceiver.Play(playStreamName);
Code 22 - The basic settings for the media stream receiver object

It can be seen that the voice data is started to be received by inviting the Play method on the stream with the unique ID parameter of the stream to be played.

For more information, please contact us at: info@voip-sip-sdk.com!