Introduction
Softphone is the short term for a software telephone and it is an extended model for the traditional telephone device. It works with VoIP technology and it is extended as it can be capable of both voice and video calling.
In this article I try to introduce a simple softphone application that can be used for voice calls. The new version of the Ozeki VoIP SIP SDK I use has just come out, so I had to modify my program in some aspects, therefore I had no time to write the video calling part yet. I will cover that topic in another article soon.
Basic Requirements for an Ozeki Softphone
A softphone application requires a PBX, as I mentioned in my previous article. The Ozeki SDK supports many PBX systems [1] and they also have guides to their configuration. I use a simple FreePBX [2] that is installed onto a virtual machine. This is an Asterisk based PBX system that seemed to be easy to use and reliable. I have actually installed it, registered two SIP accounts for testing and it works properly.
The SDK is simply installed on my computer. It unpacked its .dll file and some sample programs into the Program Files/Ozeki folder. There are a lot of license possibilities for it but I simply use the demo version that can be downloaded from their website [3]. It has some limitations but it is enough for test purposes.
The IDE I use is Visual Studio 2010 Express Edition that is free as I do not want to sell my programs, only use them for my improvement in VoIP solutions.
The Project Structure and GUI
The softphone application I wrote is a basic C# program with two classes and it only contains the basic functions of a softphone. The program has a minimal main GUI and a pop-up window that is for setting the PBX registration data.
First of all I created a Windows Forms project in Visual Studio and registered the Ozeki SDK to it as a Reference (Figure 1).
Figure 1 - Using third-party .dll as a reference
My project contains two classes for the two GUI Forms. The main class is Softphone.cs and its layout can be seen in Figure 2.
Figure 2 - The main window of my softphone
The GUI only contains the basic elements that are needed for a softphone. I used the Ozeki sample projects and the online manual [4] as a basis for my implementation, so in some aspects my program is similar to those.
The downloaded examples use in-line registration for the softphones, this means that in case of a new softphone registration, you need to modify the source code. As I wanted to make a more flexible solution, I exported the softphone registration data settings onto another window that is defined in the RegisterInfo.cs file. The GUI is simple and handy, it only contains the necessary elements for setting the registration data (Figure 3)
Figure 3 - The pop-up window for PBX registration
This registration window pops-up when the application starts and it can also be opened any time later with the Register button on the main window.
The Registration Info window only checks if the port number is set properly - actually it checks if it is a valid number. The other exception handling is in the main class, the softphone registration can invoke a VoIPException that is defined in the Ozeki SDK. This Exception occurs when you set a wrong PBX IP.
The Source Code
The RegisterInfo class is a really simple one, it contains fields for the registration data each bound to the proper textboxes and when you press the OK button these fields get their value. The code for this is shown below:
private void button1_Click(object sender, EventArgs e)
{
try
{
displayname = textBox1.Text;
username = textBox2.Text;
registername = textBox3.Text;
regpass = textBox9.Text;
domainhost = textBox4.Text + "." + textBox5.Text + "." + textBox6.Text + "." + textBox7.Text;
port = Int32.Parse(textBox8.Text);
switch (comboBox1.SelectedIndex)
{
case -1:
case 0:
natTraversal = NatTraversalMethod.None;
break;
case 1:
natTraversal = NatTraversalMethod.STUN;
break;
case 2:
natTraversal = NatTraversalMethod.TURN;
break;
}
this.Close();
}
catch (Exception ex)
{
MessageBox.Show(String.Format("The port number you set is invalid, please set a valid port number.\n {0}", ex.Message), string.Empty, MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
This code only checks the valid port number type. It is not too elegant, but serves the purpose. The window is modal any time it opens and it closes only when at least the port number is set properly.
The NAT traversal is set by a combo box and it has a default value "None" which means there will be no NAT traversal in use. You can also choose STUN and TURN from the list. These traversal types are implemented in the SDK.
If you have set the registration data, the main window opens and you can make a call, if the registration is succesful. If there is any problem with the registration, you can reopen the registration window and correct the registration data.
The Softphone.cs file contains the basic functionality. Writing a softphone needs some basic tools to be defined:
ISoftPhone softPhone;
IPhoneLine phoneLine;
PhoneLineState phoneLineInformation;
IPhoneCall call;
Microphone microphone = new Microphone();
Speaker speaker = new Speaker();
MediaConnector connector = new MediaConnector();
PhoneCallAudioSender mediaSender = new PhoneCallAudioSender();
PhoneCallAudioReceiver mediaReceiver = new PhoneCallAudioReceiver();
bool inComingCall;
RegisterInfo ri;
The most essential method in the class is the InitializeSoftphone that creates a softphone instance and registers it to the PBX:
private void InitializeSoftPhone()
{
try
{
softPhone = SoftPhoneFactory.CreateSoftPhone(SoftPhoneFactory.GetLocalIP(), 5700, 5750, 5700);
softPhone.IncomingCall += new EventHandler>(softPhone_IncomingCall);
//getting registration information
SIPAccount sa = new SIPAccount(ri.IsRegRequired, ri.displayname, ri.username, ri.registername, ri.regpass, ri.domainhost, ri.port);
NatConfiguration nc = new NatConfiguration(ri.natTraversal);
phoneLine = softPhone.CreatePhoneLine(sa, nc);
phoneLine.PhoneLineStateChanged += new EventHandler>(phoneLine_PhoneLineInformation);
softPhone.RegisterPhoneLine(phoneLine);
}
catch (Exception ex)
{
MessageBox.Show(String.Format("You didn't give your local IP adress. Please press the Register button and try it again.\n {0}", ex.Message), string.Empty, MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
The Exception that occurs here is a VoIPException that is defined in the SDK. The softphones need a port range for outgoing data and a port number for listening purposes. There are some event handlers that also need to be written in the class. For these I basically used the same methods that were written in the sample programs.
The softphone is subscribed to the incoming call event during the initialization. This event is handled in the following method:
private void softPhone_IncomingCall(object sender, VoIPEventArgs e)
{
InvokeGUIThread(() =>
{
label2.Text = "Incoming call";
label5.Text = String.Format("from {0}", e.Item.DialInfo);
call = e.Item;
WireUpCallEvents();
inComingCall = true;
});
}
The GUI labels inform you about the basic events like an incoming call. The WireUpCallEvents will subscribe the call for the CallStateChanged and the CallErrorOccured events. The call itself is basically handled in the call_CallStateChanged method:
private void call_CallStateChanged(object sender, VoIPEventArgs e)
{
InvokeGUIThread(() => { label2.Text = e.Item.ToString(); });
switch (e.Item)
{
case CallState.InCall:
microphone.Start();
connector.Connect(microphone, mediaSender);
speaker.Start();
connector.Connect(mediaReceiver, speaker);
mediaSender.AttachToCall(call);
mediaReceiver.AttachToCall(call);
break;
case CallState.Completed:
microphone.Stop();
connector.Disconnect(microphone, mediaSender);
speaker.Stop();
connector.Disconnect(mediaReceiver, speaker);
mediaSender.Detach();
mediaReceiver.Detach();
WireDownCallEvents();
call = null;
InvokeGUIThread(() => { label5.Text = string.Empty; });
break;
case CallState.Cancelled:
WireDownCallEvents();
call = null;
break;
}
}
When a call starts the media connector connects the sender and the receiver to the microphone and the speaker, and then both are attached to the call. The microphone and the speaker, of course, has to be started first. In case of the end of the call the whole process is made backwards. The tools are detached from the call and the devices are disconnected and stopped.
The other methods, like the ones for the keypad button presses, can be used without any modification in any softphone program as I saw. The Ozeki examples also contain DTMF signal handling, but I have not yet used them in my program.
What you will need to run the program
The C# source code that is attached to this article is fully executable without any modifications. You will need a PBX and two registered SIP accounts for the proper work and you will also need to download and install the Ozeki SDK and register it to the project.
When running the project, you will need to set the PBX IP, the listening port and the SIP accounts you have set in the PBX previously. After this you can have your softphones communicate with each other. If you register an IP telephone to the PBX you can also make calls between this softphone and the IP telephone too without any modifications in the code.
Conclusion
This article contained a basic introduction to softphone programming and showed a possible implementation for VoIP communication. The source code I wrote for this article is free to use and to modify by anybody.
Acknowledgment
I need to say thanks to the great developer and support teams of Ozeki Systems Ltd. who kindly answered all my questions about softphone technology and provided the possibility to try their SDK for putting my work into practice.
References
[1] http://voip-sip-sdk.com/p_103-voip-pbx-sdk-compatibility-voip.html
[2] http://www.freepbx.org/
[3] http://voip-sip-sdk.com/p_21-download-ozeki-voip-sip-sdk-voip.html
[4] http://voip-sip-sdk.com/p_100-introduction-to-ozeki-voip-sip-sdk-voip.html