For better understanding, the project has been devided into 3 parts:
  1. Part 1: SIP registration
  2. Part 2: Making and receiving calls
  3. Part 3: Controlling the calls
  4. Additional improvements: Creating call recording feature

PART 1: SIP REGISTRATION



In this section you can get to know:
  • How to add reference to Ozeki VoIP SIP SDK in Microsoft Visual Studio
  • How to create a console application softphone that will be able to register to a PBX specified by the user’s input

Build up our solution

If you want to create your own application to reproduce this solution, you needto create a new console application in Visual Studio.

Adding reference to Ozeki VoIP SIP SDK

If you would like to use the tools provided by the Ozeki VoIP SIP SDK, you have to add a reference to it in the Solution Explorer. To do this, you need to locate the “References” folder in your Solution Explorer, and:
  • right click on the References folder
  • choose “Add Reference…” option

add_references.jpg
Figure 1 - Right click on the "References" folder, than choose the "Add Reference..." option
  • select the “Browse” tab
  • click the “Browse” button
  • if You’ve installed the Ozeki VoIP SIP SDK to the default installation directory, You can find the needed VOIPSDK.dll file at the path similar to this:
C:\Program Files (x86)\Ozeki\VoIP SIP SDK\SDK\.NET4
  • select the .dll file, and click the “ADD” button

add_sdk_to_visual.png
Figure 2 - Under the Reference Manager's Browse tab, you need to browse for the "VOIPSDK.dll" file at the installation path

If you followed these steps, you can find a new line at the end of the References folder: “VoIPSDK”. This line indicates, that You can use the tools, provided by the SDK.

Classes

To separate the softphone from the user interface - from the console, this time -, we are using two classes in this example:
  • Softphone.cs: The softphone’s implementation goes here, all of it’s events, methods, functions, variables.
  • Program.cs: Our class with the Main() method, this class controls the console window, interacts with the user, by using a softphone object.

Source code analysis

To understand the source code, the softphone’s functions and their usage, we need to discuss the details. In the “using section” we need to add some extra lines, like:

   using Ozeki.Network.Nat;
   using Ozeki.VoIP;
   using Ozeki.VoIP.SDK;


Without these lines we would have to use the namespace information as a label for all tools of the SDK.

Softphone.cs

This class is used to introduce how to declare, define and initialize a softphone, how to handle some of the Ozeki VoIP SIP SDK’s events and how to use some of that’s functions. In other words, we would like to create a “telephone software”, which has the same functions (or much more), as an ordinary mobile (or any other) phone. In the Program.cs class we will use this class to create a new softphone, so we can use the functions, we can listen to the events placed here.

Objects

We need to create a softphone and a phone line object from the ISoftPhone and the IPhoneLine interfaces:

   ISoftPhone softphone;   // softphone object
   IPhoneLine phoneLine;   // phoneline object


In a constructor, we also initialize this softphone with default parameters.

   softphone = SoftPhoneFactory.CreateSoftPhone(5000, 10000, 5060);


We need to set the port range, indicated by the first two parameters as the minimum port's and the maximum port's number, this is the port's interval. The third parameter is the listening port. If we have any firewall rule which restricts the usable ports, we can set the usable port range here, which will be used during the calls. These are sample ports only in the example, but a softphone with these parameters can be used in the most of the cases . Please note that, if You are handling conference calls, or handling a lot of lines simultaneously, You will need a wide port range.

Registration to the PBX

To be able to communicate, we need to register our softphone to a PBX. To do this, the example uses the Register method. We need to create a phone line for this registration, which needs a SIP account and a NAT Traversal method.

SIP account:

At the SIP account’s creation we can set (usually, we have to set) the followings:
  • registrationRequired: is the registration required or not? This field needs to be set to “true”, if we would like to receive incoming calls.
  • displayName: a name to be displayed at the called client.
  • userName: if an other client dials this name (number), we are getting called.
  • authenticationId: an identifier to the PBX, like a login name.
  • registerPassword: the password to register to the PBX. Works in pair with the authentication ID.
  • domainHost: a domain name, an IP address, for example.
  • domainPort: Port number.

The last two values are defining the PBX to try to register to.

   var account = SIPAccount(registrationRequired, displayName, userName, authenticationId, registerPassword, domainHost, domainPort);
   We will get the values for these parameters from the user, in the Program.cs file.

NAT (Network Address Translation) Traversal:

In most of the cases we would like to communicate through firewalls. We can do it by setting the correct NAT Traversal method.

   var natConfiguration = NatConfiguration(NatTraversalMethod.None);


In the example, we don’t offer the option to set this method, we set it to “None”. This is enough for us to communicate on local network. An other option for the NAT Traversal method, is to set it to “STUN”. This way, we can define the Stun server with a NatRemoteServer object by giving the server IP, the username and password to use, as parameters:

   natConfiguration.StunServer = new NatRemoteServer(serverIP, username, password);


We can set to detect automatically which IP address we would like to use. If we set the configuration to “None”, but we also set a NatRemoteServer object with the public IP, we will use the local address on the local network, and the public on others:

   natConfiguration.AutoDetect = true;

PhoneLine:

To communicate (and to register to the PBX) we need to create a phone line. We’ve already created the necessary SIP account and the NAT Traversal setup for this creation, so with these we can do the creation:

   phoneLine = softphone.CreatePhoneLine(account, natConfiguration);


When the application is running, the phone line’s state can change. To follow these changes, we need to listen to this change event:

   phoneLine.PhoneLineStateChanged += phoneLine_PhoneLineStateChanged;


When the phone line has been created, we have to call the RegisterPhoneLine() method to register the phone line to the softphone. If the registration is set to required, this method will also send the SIP REGISTER command. We just need to use the following line:
softphone.RegisterPhoneLine(phoneLine);

Events

In this example, we are using only one; the phone line’s event. The phone line can be in several states, for example:
  • RegistrationFailed: occurs, when registration is needed, but couldn’t be made. The phone is unable to communicate through the PBX.
  • NoRegNeeded: there is no communication with the server, until the first (made) call. If we’ve set any invalid information at the SIP account’s creation, we can’t even make that call. Since our softphone is not registered to the server, we can’t receive any calls, even with valid SIP account.
  • RegistrationSucceeded: occurs, when registration is needed, and succeeded. The phone is able to receive and make calls.
  • RegistrationTimedOut: some PBX defines how long can a client be registered to it. If that time is up, the phone line’s state changes to this.

There are more states as well, but we will use only this four in this example. The using of these sates will be introduced at the Program.cs chapter.

Program.cs

This class will introduce the usage of a softphone object, handles the console events, interacts with the user, and uses the opportunities provided by the Softphone class. In this example, the softphone can only register to a PBX by the values given by the user.

First of all, we must create a softphone object.

   static Softphone mySoftphone;


We are handling everything with separated methods. These methods are communicating with each other, and making the source code more understandable and reusable.

Initialization

The first step is to initialize the softphone, we have just created. To do this, we need to call an initializer method with the following lines:

   mySoftphone = new Softphone();
   mySoftphone.PhoneLineStateChanged += mySoftphone_PhoneLineStateChanged;


Call this method in the Main() method. Now, there is a new softphone available to use, and it’s already following the phone line’s states. We will see how to handle the states below, after we are done with the welcoming and the registration process.

Showing of a greeting message

In a few lines we are introducing the application to the user. It’s preferred to tell about the available functions to use. After our softphone has been initialized, call this method to introduce the application in the Main() method.

Creating the SIP account by the user’s input and registering to a PBX

As we could see at the Softphone class, we need a SIP account to be set to be available to use the softphone for communication. We are asking the user to enter valid values to create a SIP account and to try to register to a PBX. This procedure is done with simple commands, like:

   Console.WriteLine();
   Console.ReadLine();
   You can create your own procedure to get the necessary values from the user. The example shows only a sample way to do it.


When the user set up the account, this method starts the registration process. The result of the registration will change the phone line’s state, so the next steps will be handled in that chapter. We should call this method in the Main() method, after the greetings.

Handling of the phone line states:

We are subscribed to get notified if the phone line’s state has been changed. At the mySoftphone_PhoneLineStateChanged method we can set what to do for each state, if we would like to. In this example we are handling four states: If the registration was unsuccessful, we are asking for user inputs (we are calling the correct method for that purpose), again. States for this case:

   PhoneLineState.RegistrationTimedOut


or

   PhoneLineState.RegistrationFailed


The “RegistrationFailed” state occurs, when the registration is simply unsuccessful, for example, if the user set up invalid SIP account to register. The “RegistrationTimedOut” state occurs, if the registration request takes too long (for example: in this case, we can programmatically command to try again until it succeeds, or we can ask for a new SIP account creation.)

If the registration has succeeded or there is no need for registration, we are notifying the user about it. Please note that, if there is no need for registration, but we could not reach the PBX (or we could, but with invalid SIP account), we won’t notice that until we make our first call. The states to use:

   PhoneLineState.RegistrationSucceeded
   PhoneLineState.NoRegNeeded


In the next examples, we will work with our softphone if the registration was successful, but in this example, it’s enough to notify the user about the succeeded connection.

BlockExit()

You can see a method, called BlockExit() in this Program.cs file. This method’s function is to don’t let the application to exit, so we can register (or in later examples; make or receive calls) while we would like to. You can write other solutions to solve this, but You won’t need anything like this if You are working with Windows Form applications.

PART 2: MAKING AND RECEIVING CALLS



In this section you cen get to know:
  • how to reach media handlers, handle devices and attach them to the call object
  • how to make and accept calls
  • how to handle the call’s states and events

Please note that, only the softphone’s new elements will be introduced here. You can find the registration’s process and the needed elements for that in the first example, called “SIP Registration”.

Source code analysis

To understand the source code, the softphone’s functions and their usage, we need to discuss the details. In the “using section” we need to add some extra lines, like:

   using Ozeki.Media;
   using Ozeki.Media.MediaHandlers;
   using Ozeki.Network.Nat;
   using Ozeki.VoIP;
   using Ozeki.VoIP.SDK;


Without these lines we would have to use the namespace information as a label for all tools of the SDK.

Softphone.cs

This class is used to introduce how to declare, define and initialize a softphone, how to handle some of the Ozeki VoIP SIP SDK’s events and how to use some of that’s functions. In other words, we would like to create a “telephone software”, which has the same functions (or much more), as an ordinary mobile (or any other) phone. In the Program.cs class we will use this class to create a new softphone, so we can use the functions, we can listen to the events placed here.

Objects

In the last example we’ve created a softphone and a phone line object from the ISoftPhone and the IPhoneLine interfaces. Now, we need to create some new objects:

   IPhoneCall call;
   Microphone microphone;
   Speaker speaker;
   MediaConnector connector;
   PhoneCallAudioSender mediaSender;
   PhoneCallAudioReceiver mediaReceiver;


The microphone will be connected to the mediaSender, and the speaker will be connected to the mediaReceiver, by the help of the connector object. The mediaSender and the mediaReceiver media handlers will be attached to the call object, this step is necessary if we would like to send and receive media data (for example: voice) during the communication. This sender and receiver object can be connected to different devices, media handlers as well (see below, at the connection’s chapter).
We also need a variable to indicate if we have an incoming call:

   bool incomingCall;


In a constructor, we need to initialize these new objects, variables:

   microphone = Microphone.GetDefaultDevice();
   speaker = Speaker.GetDefaultDevice();
   connector = new MediaConnector();
   mediaSender = new PhoneCallAudioSender();
   mediaReceiver = new PhoneCallAudioReceiver();
   
   incomingCall = false;


Since our softphone has no incoming call yet, we need to set the variable to “false”.

Starting and stopping the devices

There are some methods to help our later work, for example the methods to start and to stop the devices, to connect them with the other media handlers etc.
Let’s start with the device starter/stopper ones. In this example we are using a microphone and a speaker, but we could use several other devices as objects as well, for example: a webCamera.

   if (microphone != null)
   {
   microphone.Start();
   }


As You can see, this method checks first if there is a device to be started or not. After that, it starts the existing device with a single command. The method which stops them works on a similar way, with the device’s object’s Stop() command.

Connecting the media handlers

To send our voice through the microphone to the other client’s speaker, we need to connect the correct devices. Please note that, it is possible to use several different media handlers with the Ozeki VoIP SIP SDK, for example there is a similar way to connect a WaveStreamPlayback or an MP3Playback object to the mediaSender object, to play a .wav or an .mp3 file into the call, as voice (and we can receive and record voices, videos as well, and there is much more). For more information about the opportunities provided by the Ozeki VoIP SIP SDK, please read the documentation and visit the www.voip-sip-sdk.com website. You can find a lot of information and different examples there.
To connect the microphone object to the mediaSender object with the help of the connector object, we use the following lines:

   if (microphone != null)
   {
   connector.Connect(microphone, mediaSender);
   }


The connector’s first parameter is the source, and the second is the destination of the data stream. So, in the case of the speaker it looks like:

   if (speaker != null)
   {
   connector.Connect(mediaReceiver, speaker);
   }


As it shows, the mediaReceiver sends the voice to the speaker. In this example we don’t use, but You can find in the source code the “disconnector” method, which closes the connection between the two media handlers with the Disconnect() method. There is a way to close all of the connections with only one command:

   connector.Dispose();


This line closes every connection. In this example we are connecting the media handlers only once, at the initialization of the softphone, because we don’t need to disconnect them later.

Subscribing for the call’s events

In the source code, You can find two methods to help to subscribe to, and to unsubscribe the call from the call events. In this example we are subscribing to the events like this:

   call.CallStateChanged += (call_CallStateChanged);
   call.CallErrorOccured += (call_CallErrorOccured);


The first line, if the call’s state changes, and the second if an error occurs during the call.
The details of these call states will be introduced below in other methods, this one only helps to subscribe to them. The method to unsubscribe from the events works similar to the subscriber (with the “-=” operator).

Listening to the incoming calls

If we would like to be notified when there is an incoming call, we need to set an event at the softphone’s initialization for this purpose:

   softphone.IncomingCall += softphone_IncomingCall;


When the event notifies that there is a call waiting to be accepted, it sets the call object, and subscribes to the call’s events with the previously written method’s help. It also sets the incomingCall variable’s value to “false”, to indicate: there is no other call waiting.

Handling an error, occurred during the call

This example won’t do anything to handle the error itself, only notifies the user if it happens. With further developments (for example to practice), You can tell the softphone how to react, when an error occurs during a call.

Handling the call’s states

Maybe we can say, this is the main part of our softphone within the Softphone class. If the call’s state changes, an event occurs. We are handling these events within the Softphone.cs and the Program.cs file as well. In the Softphone class we are telling the softphone what to do with the media handlers, with the events, the objects, so, we are setting the call’s states to be similar to an ordinary phone’s.

In this example we are using two states by their name:

   CallState.Answered
   CallState.InCall


The “Answered” state occurs, when the call is being answered, and the “InCall” state occurs, when we are during a call, so if there is an active communication. To understand the difference between the two cases: the “Answered” state can occur only once per call, but we can enter into the same call again and again (for example if we put the call on hold, and then take the call off hold. You can find more information about this in the next example, called “Controlling the call”).

In the “Answered” state, we have to:
  • Start the devices, with the help of the previously written method.
  • Attach the media handlers to the call.
  • Please note that, we need the devices to be connected to the media sender and receiver objects, but it’s already done at the softphone’s initialization.

In this example we need these lines to attach the media handlers to the call:

   mediaReceiver.AttachToCall(call);
   mediaSender.AttachToCall(call);


In the “InCall” state, we have to:
  • Start the devices, with the help of the previously written method.
  • Please note that, the media handlers are already set to the call.

In both cases, we were subscribing to the call’s events when the call has been made (see below; at the call making chapter) or has been received (see above; when we noticed the incoming call).

We are using more states when the call has been ended: If the call ends, we can be notified about it as the IsCallEnded() method returns with a true value. For example it can occur, when an error occurs or the call enters into “Completed” state, but we can handle them all in once. When the call ends, we have to:

*Stop the devices, with the help of the previously written method.
  • Detach the media handlers from the call
  • Uunsubscribe from the call’s events
  • Finally, set the call objects to “null”

In the Program class, we will tell the softphone what to do, when the call’s state changes.

Making a call

Making a call means creating a call object, subscribing to the call’s events, and then call the call object’s Start() method. In the example the softphone checks first, if there is an already active call or not.

   if (call == null)
   {
   call = softphone.CreateCallObject(phoneLine, numberToDial);
   WireUpCallEvents();
   call.Start();
   }


If there is no active call, the we have to call the correct methods, illustrated above. To hang up the call, You can use the call object’s HangUp() method.

Accepting a call

As we could see, when the variable which indicates the incoming call has been set to "true", than there is a call to accept (so, our phone is "ringing "). In this example there is only text to notify if the phone is ringing. To make it to ring with voice, You have a lot of options provided by the SDK or just by most of the programming languages.

When we would like to accept a call, we need to set the variable's value to "false", to indicate that there are no incoming calls anymore, than we need to accept the call with the call object's Accept() method:

   if (incomingCall == true)
   {
   incomingCall = false;
   call.Accept();
   }


To hang up the call, You can use the call object’s HangUp() method.

Solving the Thread blockings

You can find a method, called “DispatchAsync(Action action)” in the source file. This method is used to solve the thread blockings, because the ReadLine() methods would block the thread.

Program.cs

This class will introduce the usage of a softphone object, handles the console events, interacts with the user, and uses the opportunities provided by the Softphone class. In this example, the softphone can accept calls automatically, and can call the number, given by the user. We are handling everything with separated methods. These methods are communicating with each other, and making the source code more understandable and reusable.

Initialization

The first step is to initialize the softphone, we have created. To do this, we need to call an initializer method. We also need to subscribe to the call’s events, to do this, we need the following lines:

   mySoftphone = new Softphone();
   mySoftphone.PhoneLineStateChanged += mySoftphone_PhoneLineStateChanged;
   mySoftphone.CallStateChanged += mySoftphone_CallStateChanged;
   mySoftphone.IncomingCall += mySoftphone_IncomingCall;
   mySoftphone.CallErrorOccurred += mySoftphone_CallErrorOccurred;


We are listening to the phone line’s and the call’s states or errors and to the incoming calls.
This method is being called in the Main() method.

Handling an error, occurred during the call

With subscribing to listen to the call’s errors, we can notify the user about an occurred error with a simple message in this example.

Handling if there is an incoming call

If there is an incoming call, our softphone notifies the user about that, and automatically accepts the call with the help of the softphone’s previously written method.

Handling the call’s states

We can reach the call’s states, since we are listening to them. When the call ends, we are calling the method which is asking the user about a number to be dialled. There are more call states, but in this tutorial we will need only this one. You can learn more about call states in the third example, called “Controlling the call”.

Handling if the registration succeeded

In the previous example (which introduced how to register to a PBX with a SIP account), we learnt how to handle the states of the phone line. We’ve notified the user about the success, and now we are telling the user to dial a number by calling a method to ask about it.

Asking the user about a number to be dialled

In this example, the phone asks the user about a number to be dialled and then stores that number as a string. We need to store the number as a string value, since it can contain special characters as well.

PART 3: CONTROLLING THE CALLS



In this section you will get to know:
  • How to put the call on hold
  • How to take the call off from hold
  • How to hang up the call
  • How to redial a number
  • How to transfer the call

Please note that, only the softphone’s new elements will be introduced here. You can find the registration’s process and the needed elements for that at the first example, called “SIP Registration”.
You can find how to make and accept calls, how to begin to handle the call’s states in the second example, called “Making and accepting calls”.

Source code analysis

To understand the source code, the softphone’s functions and their usage, we need to discuss the details.

Softphone.cs

This class is used to introduce how to declare, define and initialize a softphone, how to handle some of the Ozeki VoIP SIP SDK’s events and how to use some of that’s functions. In other words, we would like to create a “telephone software”, which has the same functions (or much more), as an ordinary mobile (or any other) phone. In the Program.cs class we will use this class to create a new softphone, so we can use the functions, we can listen to the events placed here. Please note that, this softphone is not able to accept calls. That function is removed in purpose to reduce the code’s size, and to be more understandable. We have no new objects or variables to introduce in this example.

Handling the call’s states

There are some changes in the method which handles the call’s states. We are attaching the media handlers to the call object only once, so the ideal state to do this is the “Answered” state, since it occurs only once per a call.

   mediaReceiver.AttachToCall(call);
   mediaSender.AttachToCall(call);


When the call’s state is “LocalHeld”, we are stopping the devices. Please note that, if the call is being taken off from hold, then we’ll enter into the “InCall” state (again), so we need to start the devices there as well. Since the call can enter into the InCall state several times during a call, this is the reason why we are attaching the media handlers at the “Answered” state.

Please also note that, the hold function notifies the other client to do not send data (in our case: do not talk into the microphone). In the most of the cases we do not want our voice to be heard either by to other party, while we are holding the call, so we don’t need the microphone or the speaker either, this is the reason why did we stop them.

Putting the call on hold, and taking off from hold

In this method, we are checking first, if we have an active communication or not (is the call object “null” or not), then we can start to handle the “holding”/”unholding” process. If there is an active call and we call this method, there can be two cases: if the call is in not in “LocalHeld” state, that means we haven’t put the call on hold yet, then we put the call on hold. If the call is in the “LocalHeld” state, then it indicates that we are already holding the call, so we take the call off hold. We can do it with checking the call’s state and using the commands , introduced below:

   call.HoldCall();
   call.UnholdCall();


But, the simplest way to do this:

   if (call != null)
   {
   call.ToggleHold();
   }


As we can see, with the ToggleHold() method we do not even need to check the call’s state, or store/check if the call is held or not. This method does the work for us.

Hanging up the call

We are talking about a really simple function.

   if (call != null)
   {
   call.HangUp();
   call = null;
   }


If the call exists, we just need to use the call object’s HangUp() method, and then to set the call object to “null”.

Transferring the call

Another very simple function, when we would like to transfer the call to an other destination, to an other client, there are several ways to do this, for example we can transfer the call with one of the BlindTransfer() or the AttendedTransfer() methods, or we can forward the call with the ForwardCall() one.
  • BlindTransfer(): using this during a call, the third party’s phone starts to ring, like it would be dialled first, and not ours. When the third party answers the call, we are stepping out from that.
  • AttendedTransfer(): using this during a call, we can notify the third party about the incoming call by calling it, and then redirect the call.
  • ForwardCall(): without answering the call, we can set the softphone to redirect the incoming call to an other destination. (For example: if we leave our office, we can forward the office’s phone’s incoming calls to our mobile phone.)

This example uses the BlindTransfer() method for this purpose.

   if (call != null && !string.IsNullOrEmpty(destination))
   {
   call.BlindTransfer(destination);
   }


This example uses the BlindTransfer() method for this purpose. As we can see, we are checking first, if the destination has been set, and of course we can only transfer an existing call. After that, we are using the call object's BlindTransfer() method with the other client's number as a string parameter.

Program.cs

This class will introduce the usage of a softphone object, handles the console events, interacts with the user, and uses the opportunities provided by the Softphone class. In this example, the softphone asks the user for a number to dial, and for a number to transfer the call to. The application guides us through the following steps:
  • Asks the user for two numbers (to call, and to transfer to)
  • Makes a call and when it’s being answered, puts that on hold, immediately
  • Waits for a key to be pressed to take the call off hold
  • Takes the call off hold, then hangs up the call immediately
  • Waits for a key to be pressed, than it redials the last called number
  • When the call is being answered, it’s being transferred to the other number, given by the user.

We are handling everything with separated methods. These methods are communicating with each other, and making the source code more understandable and reusable.

Initialization

This example is using three new string variables, we need to initialize them to empty:

   numberToTransfer = string.Empty;
   numberToCall = string.Empty;
   exampleSteps = string.Empty;


The numberToCall is the phone number to dial first, and then, to redial when we need to. The call will be transferred to the numberToTransfer phone number. The exampleSteps variable will guide us through the call events. We need this variable, since we would enter into the same states several times and that would cause troubles in the guiding. We can also follow the user interacts (it is mostly automatized this time) with the help of this variable by writing the user events to the console.

Asking the user about phone numbers

This method asks the user about two phone numbers (as strings). By storing the last called number (and the last incoming one) and then using that number again to dial without setting the called number manually, we've just created a function which is functioning just like the traditional telephone's redial button.

We need to call this method after the welcoming messages. When both numbers are given, the numberToCall is being dialled immediately, and the exampleSteps variable is being set to "Calling", so in the console window we can see; the user is calling the number, and the call's state is changing too.

Handling the call’s and the guide’s states.

The user is being guided through states by only pressing a few keys. For the guide we need to handle the call’s actual state and the exampleSteps variable’s value:
  • The first case, when the call is in “Answered” state and the variable’s value is “Calling”. In this case, the softphone puts the call on hold, and sets the variable to “Held”.
  • The call enters to “LocalHeld” state, sets the variable to “Unheld” and takes the call off hold, when the user presses a button.
  • The call enters to “InCall” state (again), and if the variable is “Unheld”, changes that to “HangedUp” and hangs up the call.
  • The call enters to “Complated” state, and if the variable is “HangedUp”, changes that to “Redialed”, and waits for a key to be pressed. If that happens, it redials the last called number.
  • When the other phone answers the call, it enters into “Answered” state, and if the variable is “Redialed”, the call is being transferred to the other phone number immediately. The variable’s value is also changed to “Transfering”.

By writing the exampleSteps variable’s value to the console, we can see what would happen, if a real user would use these functions, actions.

Summary



To sum it up, building your own VoIP softphone can be pretty easy and fast if you use previously written components. Ozeki’s VoIP SIP SDK is a really effective tool if you want to focus on developing your application instead of worrying about necessary network protocols and technical details. As simple as that!

ADDITIONAL IMPROVEMENTS


How to improve your .NET VoIP softphone by creating call recording feature

If you have a working softphone, you can add some extra functionalities to it. After you have added them the call recording will be done automatically.

You can record the incoming and outgoing voices separately in two different .wav files or together in one. In either way you will need the WaveStreamRecorder media handler object to capture the audio into .wav files.

As I mentioned earlier if you want to capture both the incoming and outgoing voice in one file you will need an AudioMixerMediaHandler. It is going to mix the incoming and outgoing voices together. You can see the implementation of these two objects below.

   WaveStreamRecorder recorder;  
   AudioMixerMediaHandler mixer = new AudioMixerMediaHandler();  
     
   string filename;  
   string caller; 


You can see two strings below the objects, these specify the name of the recorded file so, that the filename will be the parameter for the recorder’s constructor. The caller will be the dialled number in case of an outgoing call or the displayed name of the actual caller in case of an incoming call.
In case of an outgoing call you will have to put the caller into the Pick Up button’s event handler as you can see below.

   private void PickUp_Click(object sender, EventArgs e)
           {
               MessageBox.Show("Please note, that all your calls made by this softphone will be recorded.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
               if (inComingCall)
               {
                   inComingCall = false;
                   call.Accept();
                   return;
               }
   	
               if (call != null)
                   return;
   
               if (string.IsNullOrEmpty(DialedNumber.Text))
                   return;
   
               if (phoneLineInformation != PhoneLineState.RegistrationSucceeded && phoneLineInformation != PhoneLineState.NoRegNeeded)
               {
                   MessageBox.Show("Phone line state is not valid!");
                   return;
               }
               caller = DialedNumber.Text;
               call = softPhone.CreateCallObject(phoneLine, DialedNumber.Text);
               WireUpCallEvents();
               call.Start();
           }


In case of an incoming call you will have to put the caller into the softPhone_IncomingCall() method.

        private void softPhone_IncomingCall(object sender, VoIPEventArgs<IPhoneCall> e)
        {
            InvokeGUIThread(() =>
            {
                RegistrationState.Text = "Incoming call";
                DialedNumber.Text = String.Format("from {0}", e.Item.DialInfo);
                caller = e.Item.DialInfo.DisplayName;
                call = e.Item;
                WireUpCallEvents();
                inComingCall = true;
            });
        }


When the call state becomes Answered, the application has to start the call recording. The recorder is going to be initialized with the filename that includes the caller string and the current time. The current time is given in the form of hours, minutes and seconds.

The .wav file is going to be stored in the folder of the exe of the application, because there is no path specified for it.

Connect the microphone and the mediaReceiver objects into the mixer then connect the mixer to the recorder. This way the mixed audio stream is going to be recorded into the file that is specified by the filename.

                case CallState.Answered:
                    if (microphone != null)
                        microphone.Start();
                    connector.Connect(microphone, mediaSender);
                    
                    filename = caller + "-" + DateTime.Now.Hour.ToString()+"-"+ DateTime.Now.Minute.ToString()+"-"+DateTime.Now.Second.ToString()+".wav";
                    recorder = new WaveStreamRecorder(filename);

                    connector.Connect(microphone, mixer);
                    connector.Connect(mediaReceiver, mixer);
                    connector.Connect(mixer, recorder);
                    if (speaker != null)
                       speaker.Start();
                    connector.Connect(mediaReceiver, speaker);

                    mediaSender.AttachToCall(call);
                    mediaReceiver.AttachToCall(call);

                    recorder.StartStreaming();

                    break;


The recording will be started by calling the recorder.StartStreaming() method. Of course when the call ends the recording has to stop and the media handlers have to be disconnected too.

                case CallState.Completed:
                   recorder.StopStreaming();

                    connector.Disconnect(microphone, mixer);
                    connector.Disconnect(mediaReceiver, mixer);
                    connector.Disconnect(mixer, recorder);
                    if (microphone != null)
                        microphone.Stop();
                    connector.Disconnect(microphone, mediaSender);
                    if (speaker != null)
                        speaker.Stop();
                    connector.Disconnect(mediaReceiver, speaker);

                    mediaSender.Detach();
                    mediaReceiver.Detach();

                    WireDownCallEvents();
                    call = null;

                    InvokeGUIThread(() => { DialedNumber.Text = string.Empty; });
                    break;
                case CallState.Cancelled:
                    WireDownCallEvents();
                    call = null;
                    break;


The WaveStreamRecorder object is subscribed for the stopped event as you can see it on the following code example.

        void WaveStreamRecorder_Stopped(object sender, EventArgs e)
        {
            if (RecorderStopped != null)
                RecorderStopped(this, EventArgs.Empty);
        }

        public event EventHandler<EventArgs> RecorderStopped;


It is wise to inform the user of the application every time he/she makes a call that it is going to be recorded. I have made a pop up window for this reason that will appear every time the Pick Up button is pressed.

private void PickUp_Click(object sender, EventArgs e)
{
	MessageBox.Show("Please note, that all your calls made by this softphone will be recorded.", "", MessageBoxButtons.OK, MessageBoxIcon.Information);
…


With this you have successfully implemented the call recording into your softphone. Go ahead make a test call and see how it works.

Get more information about softphone development



Download required software:



Further VoIP example projects:



Good luck!

Last edited Mar 25, 2014 at 10:27 AM by simonrobert, version 9