Download: | web-sip-designed-gui.zip |
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>(); }
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(); }
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); } }
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; }
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; }
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);
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); }
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; }
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);
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); }
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(); } }
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); }
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;
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); }
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)
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 InterfaceThe 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(); }
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; }
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); }
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 )
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)); }
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); }
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);
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!