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.
Acknowledgement
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