Introduction
VoIP
technology is one of the fastest growing technologies today. It is used for
voice communication over the Internet with high quality sound and due to its
wide penetration it is supported by many devices. However many mobile service
providers block the usage of the port 5060, which is used for SIP protocol.
(SIP protocol is used in VoIP communications). This means that many mobile
service providers block the usage of VoIP on your mobile phones.
I have come up with a solution for this problem.
It is a WP7 client application that communicates with a server through TCP/UDP
protocol and the server communicates with a PBX through SIP protocol. This way
the mobile service providers will not block the VoIP communication because they
will only see the TCP/UDP communication between the client and the server.
Background
I
have used the Windows Phone SDK 7.1 together with the VoIP SIP SDK offered by
Ozeki, which has great and easily manageable tools for VoIP communication,
along with Microsoft Visual studio 2010. For the Visual Studio at least .NET
Framework 3.5 SP1 is needed or any newer version of it. I have written the
application in the C# programming language.
Start by building the client
First of
all I have created a new Windows Phone application project in Visual Studio and
set the WPClientSDK.dll as a reference in my project because I needed it for my
application. For this .dll I had to download the Ozeki VoIP SIP SDK because it
is the part of it.
After I
have created the new Windows Phone application project in Microsoft Visual
Studio I started to edit the graphical interface of the application, which I
could do in the MainPage.xaml file of the client project. You can see the code
of the interface I have made below:
<!---->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!---->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="Ozeki VoIP SIP SDK" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="Mobile2Web" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!---->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Button Content="Call" Height="70" x:Name="btnCall" VerticalAlignment="Top" Click="btnCall_Click" Margin="57,156,223,0" IsEnabled="False" />
<TextBlock x:Name="txtboxInfo" TextWrapping="Wrap" Text="Offline" TextAlignment="Center" VerticalAlignment="Top" FontSize="24" Margin="0,86,0,0"/>
<Button Content="Stop Call" Height="70" Margin="215,156,66,0" Name="btnStopCall" VerticalAlignment="Top" Click="btnStopCall_Click" IsEnabled="False" />
<TextBlock Height="23" HorizontalAlignment="Center" Margin="0,27,0,0" Name="txtBDebug" Text="DebugLine" VerticalAlignment="Top" Visibility="Collapsed" />
<TextBlock Height="23" HorizontalAlignment="Right" Margin="0,27,18,0" Name="txtBClientID" Text="ClientID" VerticalAlignment="Top" FontSize="21.333" />
<TextBox x:Name="txtLog" Height="348" HorizontalAlignment="Left" Margin="0,253,0,0" Text="" VerticalAlignment="Top" Width="450" IsEnabled="False" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" />
</Grid>
</Grid>
Code example 1: The
code of the graphical user interface
Figure 1: The
graphical user interface
As you can
see the application has the necessary buttons for the voice communication e.g.:
the ‘Call’ and ‘Stop Call’ buttons. You can also see some basic information
about the connection and calls in the textbox.
For the client application to work as I wanted I
had to use some Windows Phone 7 and VoIP supportive packages. I have set them
as precompilation packages in the MainPage.xaml.cs file of the client, so I
could use them without namespace labelling. You can see them below:using System;
using System.Diagnostics;
using System.Windows;
using Microsoft.Phone.Controls;
using Ozeki.MediaGateway;
using WPClientSDK;
using WPClientSDK.Log;
Code example 2: Necessary
packages
I had to
use some basic tools for the VoIP communication, which are the streamed media
handlers, the audio player and the microphone handler classes. You can see them
in the code below:
public partial class MainPage : PhoneApplicationPage
{
private MediaConnection connection;
private MediaStreamSender streamSender;
private MediaStreamReceiver streamReceiver;
private AudioPlayer audioPlayer;
private Microphone microphone;
private string clientID;
private string IncomingCallOwner;
private bool callProcess;
Code example 3: Tools
for the VIP communication
When the
client application starts the first event that invokes is the MainPage_Loaded
event. I have set the media connection and the microphone settings in the event
handler of the MainPage_Loaded event. You can see my private IP address in the
MediaConnection event. If you want to build this application for yourself you
will have to change the address to yours.
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
Logger.Instance.LogMessageReceived += new EventHandler<GenericEventArgs<string>>(Instance_LogMessageReceived);
connection = new MediaConnection("192.168.115.181:6888");
connection.ConnectionStateChanged += new EventHandler<GenericEventArgs<ConnectionState>>(connection_ConnectionStateChanged);
connection.Client = this;
connection.Connect();
microphone = Microphone.GetMicrophone();
}
Code example 4: Connection
to the server and getting access to the microphone
The phone
application has to be initialized, which is done in the code snippet below:
public void OnSetReadyStatus(bool isReady, string name)
{
InvokeGUIThread(()=>
{
clientID = name;
btnStopCall.IsEnabled = isReady;
btnCall.IsEnabled = isReady;
txtBClientID.Text = name;
txtboxInfo.Text = isReady ? "Ready to call." : "Waiting for other client.";
});
}
Code example 5: Initialization
of the client
Now
the application is ready to accept and make phone calls so let’s see the code
responsible for them.
private void btnCall_Click(object sender, RoutedEventArgs e)
{
if (Microphone.GetPermissionToMicrophone())
{
ReleaseStreams();
callProcess = true;
if (!string.IsNullOrEmpty(IncomingCallOwner))
{
connection.InvokeOnConnection("ChangeToIncall");
IncomingCallOwner = "";
btnCall.IsEnabled = false;
}
else
{
connection.InvokeOnConnection("Call", clientID);
txtboxInfo.Text = "Outgoing call progress.";
btnCall.IsEnabled = false;
}
}
else
{
txtboxInfo.Text = "Please, add permission to access microphone.";
}
}
Code example 6: The
button for accepting or starting calls
As you can
see this code first checks if the microphone is available and if it is, then it
can either accept an incoming call or it can start an outgoing. Otherwise it
will tell you to give access to the microphone.
When you are in a call it is just natural that
you have to be able to hang it up, regardless of if you have been called up, or
you started the call. So you can see the code of hanging up a call next.
private void btnStopCall_Click(object sender, RoutedEventArgs e)
{
if (callProcess)
{
txtboxInfo.Text = "Call stop, ready to call.";
connection.InvokeOnConnection("CallStop");
ReleaseStreams();
btnCall.IsEnabled = true;
}
}
Code example 7: The
button for stopping the call
The
application displays when you are receiving a call and it shows who the caller
is. I have implemented this with the following code.
public void OnCallRequest(string remotpartyId)
{
callProcess = true;
IncomingCallOwner = remotpartyId;
InvokeGUIThread(() => { txtboxInfo.Text = "Call received from " + remotpartyId; });
}
Code example 8: The
method for receiving calls
Figure 2: Call
received from client0
When
the call is established the method that can be seen below, which is called
OnInCall will be invoked on the client application. It sets the media sender
for the call and sets the microphone as voice capturing device.
public void OnInCall()
{
InvokeGUIThread(()=>txtboxInfo.Text = "Incall");
streamSender = new MediaStreamSender(connection);
try
{
streamSender.AttachMicrophone(microphone);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
streamSender.StreamStateChanged += new EventHandler<GenericEventArgs<StreamState>>(streamSender_StreamStateChanged);
streamSender.Publish(clientID);
}
Code example 9: Changing
the state of the client to Incall
Figure 3: The
client in Incall state after accepting the call
In
VoIP communication the media is streamed between the parties and for this I
have implemented a stream receiver and a player object. You can see their code
below.
public void OnPlayRemoteStream(string remoteparty)
{
streamReceiver = new MediaStreamReceiver(connection);
streamReceiver.StreamStateChanged += new EventHandler<GenericEventArgs<StreamState>>(streamReceiver_StreamStateChanged);
audioPlayer.AttachMediaStreamReceiver(streamReceiver);
streamReceiver.Play(remoteparty);
}
Code example 10: Playing
the audio of the clients for each other
The
stream receiver and sender objects need debugging functionality, which I have
implemented by subscribing them for the StreamStateChanged event with two event
handler methods.
void streamReceiver_StreamStateChanged(object sender, GenericEventArgs<StreamState> e)
{
Debug.WriteLine("receiver play {0}", e.Item);
InvokeGUIThread(() => { txtBDebug.Text = e.Item.ToString(); });
}
void streamSender_StreamStateChanged(object sender, GenericEventArgs<StreamState> e)
{
InvokeGUIThread(() => { txtBDebug.Text = e.Item.ToString(); });
switch (e.Item)
{
case StreamState.PublishingSuccess:
connection.InvokeOnConnection("PlayRemoteStream");
break;
default:
Debug.WriteLine(e.Item);
break;
}
}
Code example 11: Debugging
the stream receiver and sender objects
When the
call stops the media streams have to be released and the GUI will change back
to the state of the new phone. It means its going to be ready to make a call or
in case of an incoming one to answer it.
public void OnCallStop()
{
InvokeGUIThread(()=>
{
txtboxInfo.Text = "Remoteparty finished the call.";
btnCall.IsEnabled = true;
} );
ReleaseStreams();
}
Code example 12: The
method that allows to make or answer new calls after the call has stopped
Figure 4: The other client hanged up the call (left) and This client hanged up the call (right)
The
ReleaseStreams( ) method releases the MediaStreamSender or MediaStreamReceiver
objects whenever a call is started or stopped, because in either cases the
streaming media has to be stopped. When starting a call the ReleaseStreams ( ) is
used as a verification that no media is being streamed from the given client.
private void ReleaseStreams()
{
if (streamSender != null)
{
streamSender.StreamStateChanged -= streamSender_StreamStateChanged;
streamSender.Close();
}
if (streamReceiver != null)
{
streamReceiver.StreamStateChanged -= streamReceiver_StreamStateChanged;
streamReceiver.Close();
}
}
Code example 13: The
method for releasing the streams
Building the server
The
server side of the application is a console application that can handle more
client types. You can set the type of the client you want to use with the
server in the App.config file. I am using windowsphone in this example so the
code below speaks for itself.
<add type="windowsphone" serviceName="Web2WebServer" listenedPort="6888" policyServiceEnabled="true"/>
Code example 14: Definition
of the client type
The
server is basically an Ozeki MediaGateway class you can see its definition and
the necessary properties for it.
class Mobile2WebGateway : MediaGateway
{
private Dictionary<IClient,MyClient> Clients;
private int clientCounter;
private int busyClients;
Code example 15: Defining
the Mobile2WebGateway as a MediaGateway class
When you
start the server application it calls the Mobile2WebGateway ( ) and OnStart ( )
methods, which works as the code shows below.
public Mobile2WebGateway()
{
Console.WriteLine("Mobile2Web Gateway starting...");
}
public override void OnStart()
{
Clients = new Dictionary<IClient, MyClient>();
Console.WriteLine("Mobile2Web Gateway started.");
}
Code example 16: Starting
the server
Figure 5: The
server consol after it is started
The
server handles the client connections with the OnClientConnect ( ) method which
writes to the consol the IP address of the client that has connected to the
server and notifies the other clients about the new connected client.
public override void OnClientConnect(IClient client, object[] parameters)
{
Console.WriteLine( "{0} client connected to the server.",client.RemoteAddress);
if (!Clients.ContainsKey(client))
{
Clients.Add(client, new MyClient(client, string.Format("client{0}", clientCounter++)));
NotifyClientsAboutTheirCallStatus();
}
}
Code example 17: Client
connection
Figure 6: The
first client has connected
The
server handles the disconnection of the clients too with the OnClientDisconnect
( ) method which works just like the connection method. It writes the IP
address of the disconnected client to the consol and notifies the other clients
about the disconnected client.
public override void OnClientDisconnect(IClient client)
{
if (Clients.ContainsKey(client))
{
MyClient disconnectedClient = Clients[client];
if (disconnectedClient.IsBusy && disconnectedClient.RemoteParty!=null)
disconnectedClient.RemoteParty.OnCallStop();
Console.WriteLine("{0}, {1} disconnected from the server.", client.RemoteAddress,disconnectedClient.Name);
Clients.Remove(client);
NotifyClientsAboutTheirCallStatus();
return;
}
Console.WriteLine("{0} client disconnected from the server.", client.RemoteAddress);
}
Code example 18: Client
disconnection
Figure 7: The
first client has disconnected
When both
clients are connected to the server they can call each other. The server can
handle the calls with the call ( ) method, which works between two clients, who
will be set as remote party to each other.
public void Call(IClient invokerClient, string requestOwner)
{
foreach (KeyValuePair<IClient, MyClient> keyValuePair in Clients)
{
if (keyValuePair.Key != invokerClient && !keyValuePair.Value.IsBusy)
{
MyClient invoker = Clients[invokerClient];
MyClient callee = keyValuePair.Value;
invoker.RemoteParty = callee;
callee.RemoteParty = invoker;
callee.OnCallRequest(requestOwner);
return;
}
}
}
Code example 19: The
method of call
If
the call is established between the two clients we have to set their states
into InCall and notify the other clients about their state.
public void ChangeToIncall(IClient client)
{
Clients[client].OnInCall();
foreach (MyClient c in Clients.Values)
{
NotifyClientsAboutTheirCallStatus();
}
}
Code example 20: Changing
the clients state to Incall
After
the call is established and the clients start to publish their streams the
server has to handle them. It does it with the following method.
public override void OnStreamPublishStart(IClient client, IMediaStream mediaStream)
{
Console.WriteLine("client : {0} publish his stream : {1}",client.RemoteAddress,mediaStream.Name);
base.OnStreamPublishStart(client, mediaStream);
}
Code example 21: The
clients have started publishing their streams
The
server has to start playing the clients the other’s media stream so they will
be able to hear each other. It does it whit the following code.
public void PlayRemoteStream(IClient client)
{
{
Clients[client].OnPlayRemoteStream();
}
}
Code example 22: The
server plays the streams for the clients
Figure 8: The
server plays the streams of the clients for them after they have made a call
When the
clients stop their calls the server has to stop their media being streamed and
it hast to change their states to available again from InCall. It does it by
calling their OnCallStop ( ) method as you can see in the following code.
public void CallStop(IClient invokerClient)
{
if (Clients.ContainsKey(invokerClient))
{
MyClient invoker = Clients[invokerClient];
invoker.RemoteParty.OnCallStop();
}
}
Code example 23: The
server stops the call by invoking the clients OnCallStop method
You could
see that whenever a client started a call or received an incoming call its
state changed and all the other clients were notified about this change. You
can see the method which is responsible for this below.
private void NotifyClientsAboutTheirCallStatus()
{
busyClients = 0;
foreach (MyClient c in Clients.Values)
{
if (c.IsBusy)
busyClients++;
}
bool isReady = Clients.Count > 1 && Clients.Count-busyClients > 1;
lock (Clients)
{
foreach (KeyValuePair<IClient, MyClient> keyValuePair in Clients)
{
if (!keyValuePair.Value.IsBusy)
keyValuePair.Value.OnSetReadyStatus(isReady, keyValuePair.Value.Name);
}
}
}
Code example 24: The
method for notifying the client about the change of the state of a client
In
the MyClient.cs file the server has methods which write to the consol and they
will invoke the actual, appropriate client side methods while the server is
running. You can see them below.
public void OnStartPlay(string remotpartyId)
{
Client.InvokeMethod("OnPlay", remotpartyId);
}
public void OnSetReadyStatus(bool isReady, string name)
{
try
{
Client.InvokeMethod("OnSetReadyStatus", isReady, name);
}
catch (Exception)
{}
}
public void OnCallRequest(string requestOwner)
{
Console.WriteLine("Call request received from {0} to {1}",requestOwner, Name);
RemoteParty.IsBusy = true;
IsBusy = true;
Client.InvokeMethod("OnCallRequest", requestOwner);
}
public void OnInCall()
{
Console.WriteLine("Sends 'start publishing' sign to the clients.");
Client.InvokeMethod("OnInCall");
RemoteParty.Client.InvokeMethod("OnInCall");
}
public void OnPlayRemoteStream()
{
Console.WriteLine("PlayRemoteStream - client Name : {0} starts to play remoteStream: {1}", RemoteParty.Name, Name);
RemoteParty.Client.InvokeMethod("OnPlayRemoteStream", Name);
}
public void OnCallStop()
{
IsBusy = false;
RemoteParty.IsBusy = false;
Client.InvokeMethod("OnCallStop");
}
Code example 25: The
methods of the MyClient class
Summary
To
sum it up I have created a Windows Phone 7 application that allows VoIP
communication on Windows mobile phones with every mobile service provider
through TCP/UDP communication. For this solution I have created a server and a
client application in Microsoft Visual Studio with Windows Phone SDK 7.1 and
the Ozeki VoIP SIP SDK. I have chosen Ozeki VoIP SIP SDK because everything I
needed for this application was already implemented in it. I only had to use
the proper methods and classes it provides, so I can recommend it to anybody
who wants to develop VoIP applications and does not wish to create everything
from scratch. This is a great tool if you want to focus on developing your
application and not on the necessary network protocols and technical details.
References