Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to build a VoIP application for Windows Phone 7 that will not be blocked by the service providers

0.00/5 (No votes)
7 Feb 2014 1  
It describes how to develop a WP7 VoIP client application that communicates with a server through TCP/UDP protocol

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: 

<!--LayoutRoot is the root grid where all page content is placed-->
 <Grid x:Name="LayoutRoot" Background="Transparent">
     <Grid.RowDefinitions>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="*"/>
     </Grid.RowDefinitions>

     <!--TitlePanel contains the name of the application and page title-->
     <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>

     <!--ContentPanel - place additional content here-->
     <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)
            {
                //Searchs the first not busy connected client and sets theirs remote party.
                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)
        {
            //foreach (MyClient c in Clients.Values)
            {
                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  

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here