Course 1 / Lecture 3

How to develop a softphone in C#

How to make and accept calls using SIP/VoIP

Here, you can learn how to make and receive calls with the help of the call's states and events, how to use MediaHandlers and much more.
This example continues to develop the "SIP Registration" example, where you could learn how to register a softphone to a PBX with a SIP account, using Ozeki VoIP SIP SDK.

In this tutorial example you can learn the followings:

  • 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

What knowledge would you need?

To fully understand this guide, you might need to study the following chapters first:

  • SIP registration: you can learn how to begin softphone developing and how to be able to register to a pbx with a sip account.
    Learn more...
  • Managing media handlers: you can find here examples and a simple guide about how to be able to use, connect and manage different media handlers, and how can you attach them to calls.
    Learn more...

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.MediaHandlers;
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.

What is Softphone.cs used for?

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.

What objects does the Softphone class uses?

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

How to start and stop 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.

How to connect 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). 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.

How to subscribe to the call's events?

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

call.CallStateChanged += (call_CallStateChanged);

This event handle, if the call's state changes, or if an error occurs during the call. The details of this call state will be introduced below in other methods, this one only helps to subscribe to them. The method to unsubscribe from the event works similar to the subscriber (with the "-=" operator).

How to listen to 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.

How to handle errors, 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.

How to handle 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 three states by their name:

CallState.Answered
CallState.InCall
CallState.Error

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
  • Unsubscribe 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.

How to make 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.

How to accept 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.Answer();
}

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.

What is Program.cs used for?

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.

How to initialize the softphone?

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.RegistrationStateChanged += mySoftphone_PhoneLineStateChanged;
mySoftphone.CallStateChanged += mySoftphone_CallStateChanged;
mySoftphone.IncomingCall += mySoftphone_IncomingCall;

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.

How to handle errors, occurred during the call?

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

How to handle incoming calls?

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.

How to handle 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".

How to handle 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.

Conclusion

After reading this documentation and studying the source code, we must be familiar about how to make the audio handlers to work, how to connect them to the actual call object, how to handle the necessary events. We also learnt how to make and accept calls.

The next example will introduce how to:

  • Put the call on hold
  • Take the call off hold
  • Transfer a call
  • Hang up the call
  • Redial

If you have any questions or need assistance, please contact us at info@voip-sip-sdk.com

Related Pages

More information