Course 1 / Lecture 9

How to develop a softphone in C#

How to build Predictive Autodialer in C# with Ozeki VoIP SIP SDK

The following guide introduces how to develop a softphone, which handles a ring group (group of agents), and a list of clients, who are need to be called. The main function of this example application is to call the clients from the list one by one, than render an agent to them.
To fully understand this guide, you might have to visit the Autodialer article first, since this guide is about to continue to develop that.

What is predictive autodialer?

An autodialer is an electronic device or software that automatically dials telephone numbers, and once a call has been answered, the autodialer either plays a recorded message or connects the call to a live person.
When an autodialer connects an answered call to a live agent, it is often called a "predictive dialer" or "power dialer". A predictive dialer uses realtime analysis to determine the optimal time to dial more numbers, whereas a power dialer simply dials a pre-set number of lines when an agent finishes the previous call.
From this example you can learn how to create an autodialer softphone, which receives the phone numbers from a csv file, and connects them with an available ring group member. The application is written in c# with using Ozeki VoIP SIP SDK.

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...
  • Making and accepting calls: from this guide you can learn how can your softphone make and accept calls, and handle the calls' states.
    Learn more...
  • Parallel call management: from this guide you can learn how can you develop softphones, which are able to manage multiple calls simultaneously.
    Learn more...
  • How to build an Autodialer: this simple autodialer application is going to be continued and changed during this example, so it's highly recommended to study the simplier example first.
    Learn more...

How to develop a predictive autodialer softphone in C#?

Since the predictive autodialer softphone should be able to communicate with users, read and parse (.csv) files, make and manage parallel calls, manage ring group members and much more, it's highly recommended to design and separate functions and classes providently.

The example is using six classes:

  • Softphone.cs: a simple softphone which is able to register to a PBX, and can create call objects.
  • Program.cs: a class to communicate with users; asks them about register information, number of ring group member agents and their phone numbers, amount of simultaneously makeable calls and a .csv file path.
  • CallInfo.cs: instances of this class are functioning as types; each CallInfo objects represents a line within the csv file, and contains a phone number and a message separately. (The message is being used as a note about the client, in this example.)
  • ConfigStore.cs: stores and manages the lists used by the application, and it has only one instace which provides access to the lists' methods.
  • CallHandler.cs: since the autodialer softphone should be able to manage calls simultaneously, the softphone can create instances from this class for each of the calls, to handle those separately in the same time.
    In the other hand, the application won't transfer the agents and clients to each other, just sends the voice data through itself.
  • Autodialer.cs: creates CallHandler objects for the purpose to handle the calls defined by the CallInfo objects, and also handles the list of clients, and creating the callhandler objects to them when the application indicates this intent through events.

Please note that, this guide is continuing to develop the Autodialer example, so you will find here only the new steps, features, and the differences between the two application examples.

What is Softphone.cs used for?

The autodialer's softphone class is able to register to a pbx, provides information about the phone line's sate, and also creates call objects, when needed.
Please note that, since the softphone doesn't need to be able to receive incoming calls, it shouldn't be registered to a PBX, which means that the registrationRequired field of the sip account can be set to "false". Without registering to a pbx, the autodialer is still able to dial phone numbers through the pbx, if that is reachable and allows it.
To learn more about the Softphone class, please visit the Autodialer article.

What is Program.cs used for?

The Program class uses a Softphone instance, handles the phone line's state, asks the user about sip account information, amount of simultaneously makeable calls and a .csv file path, reads, parses the given file and creates CallInfo objects to represent the file's lines.

The only new main task of this class is to ask the user about the number of the ring group member agents, and get their phone numbers through the console window:

static void AgentAdder()
        {
            _memberCount = 0;

            while (_memberCount == 0)
            {
                Console.Write("\nThe number of the RingGroup members: ");
                try
                {
                    _memberCount = Int32.Parse(Console.ReadLine());
                }
                catch
                {
                    Console.WriteLine("Wrong input!");
                }
            }

            for (int i = 0; i < _memberCount; i++)
            {
                Console.Write(" {0}. member's phone number: ", i + 1);
                string member = Console.ReadLine();
                _configStore.AddAgent(member);
                _configStore.AddFreeAgent(member);
            }
        }

Please note that, even if you set larger number for possible outgoing calls, the application will not manage more calls at a time than the maximum number of agents.
When all of the previous steps are done, it calls the Autodialer class's Start() method.
To learn more about the Program class, please visit the Autodialer article.

What is CallInfo.cs used for?

Each CallInfo object represents a line within the csv file, which is being used as a complex value, as it stores a phone number and a message which being used as a note about the client in this example. CallHandler objects will be created for all of the CallInfo objects, to manage the calls separately.

What is ConfigStore.cs used for?

To store the lists and provide access to it's method through one instance, the application is using this class. It stores three lists, and provides the neccessary methods to reach, add or remove elements from it:

  • List of agents: stores all of the agents of the ring group, and the class provides a method to add new agents to the list. The program class uses this method, when asks the user about the agents.
  • List of agents: stores the available agents, who are not in call at the moment. Provides three methods: to remove or add an agent, and to get the available ones. The CallHandler class is using these methods, since it works with the available agents' list.
  • List of clients to be called: stores the client's phone numbers and notes (messages) as CallInfo objects, and also provides two methods: one to add new clients, and one to get the list of them. The Program class is using the adder method of this list when asks the user about a csv file, and the CallHandler class is using the list to call the clients one-by-one.

What is CallHandler.cs used for?

The softphone can handle multiple calls simultaneously, and each of those is being handled by a CallHandler instance, set by a CallInfo object. Since a CallInfo object stores a phone number, the call will be created to that number, and if the call is being accepted by the client, a ring group group will be called with the RingAll strategy. To read more about ring group implementation and ring strategies, please read the Ring Group guide.
When a group agents answeres the call, the audio data will be going through this softphone, instead of transfering the calls to each other.

How are calls being made?

Calls are being made one by one; first of all, a client is being called from the list, and if the call is being answered, the softphone calls the ring group. If the maximum amount of simultaneously makeable calls hasn't reached yet, it calls the next client from the list.

Usually, calls are being made as the following steps:

  1. The client is being called, and accepts the call.
    public void Start()
    {
        lock (_sync)
        {
            _call = _softphone.CreateCall(_callInfo.PhoneNumber);
            _call.CallStateChanged += ClientCallStateChanged;
            mediaReceiverFromClient.AttachToCall(_call);
            mediaSenderToAgent.AttachToCall(_call);
            connectorFromClient.Connect(mediaReceiverFromClient, mediaSenderToAgent);
            _call.Start();
        }
    }
    
  2. The available agents are being stored at the ConfigStore class's list. After receiving the list of free agents, they are being called and added to a new list; the list of agents who are being called for the purpose to one of them accept the call. The number of these agents are being stored in the _ringingAgents integer value, which can be checked later when the application needs to check if there is a free agent or not.
    void CheckAgents()
            {
                lock (_sync)
                {
                    _ringingAgents = 0;
                    foreach (var freeAgent in _configStore.GetFreeAgents())
                    {
                        var callAgent = _softphone.CreateCall(freeAgent);
                        callAgent.CallStateChanged += AgentCallStateChanged;
                        _freeAgentChecks.Add(callAgent);
                        _ringingAgents++;
                    }
    
                    foreach (var freeAgentCheck in _freeAgentChecks)
                    {
                        freeAgentCheck.Start();
                    }
                }
            }
    
  3. The application checks if it can make an other call (as the user set the maximum amount of concurrent calls):
    • next call can be made: calls an other client from the list (call's sequence begins again at step 1.).
    • next call can't be made: waits for a call to end. If that happens, it calls an other client from the list (call's sequence begins again at step 1.).

The predictive autodialer stops working when all of the phone numbers from the list were tried to be called.

However, there are several possible outcomes, for example:

  1. The client could not be reached (for example: busy, or error occured).
  2. The next client is being called on the list.

Another special outcome:

  1. The client answeres the call.
  2. The ring group is being called, but every agents reject the call. (For example: if nobody would like to talk with the client.)
  3. The client's call is being hanged up, and the next client is about to be called.

How are calls being connected together?

The application is using media sender and media receiver objects for the purpose to send audio data from one to another. This means the following steps, for example:

  1. A client is being called, and when the call is being accepted, the call handler attaches a media sender and a media receiver to this call.
  2. When an agent accepts the call, the SetupAgentDevices() method is being called, and the media handler attaches a media sender and a media receiver to this call, as well:
    void SetupAgentDevices(IPhoneCall call)
        {
            lock (_sync)
            {
                _agentCall = call;
                mediaReceiverFromAgent.AttachToCall(call);
                mediaSenderToClient.AttachToCall(call);
                connectorFromAgent.Connect(mediaReceiverFromAgent, mediaSenderToClient);
            }
        }
    

With a connector object, the the media senders and media receivers are being connected together, so it simply sends the outgoing voice data from one, to the incoming voice data of the other one. You could already see the connections within the previously introduced code snippets:

connectorFromClient.Connect(mediaReceiverFromClient, mediaSenderToAgent);

connectorFromAgent.Connect(mediaReceiverFromAgent, mediaSenderToClient);

What happens during the client's call?

After a client from the list is being called, the application follows the call's state to do different tasks:

  • Call is being answered: when the call is being answered, the ring group is being called for the purpose to reach an agent and connect their devices together.
    if (e.State == CallState.Answered)
        {
            lock (_sync)
            {
                Console.WriteLine("\nCall has been accepted by {0}.", _callInfo.PhoneNumber);
                Console.WriteLine("Note about the callee: \n\"{0}\"", _callInfo.Message);
                CallRingGroup();
            }
        }
    
    If the free agents' list contains any agents, it tries to call them. You can find the CheckAgents() method's implementation above.
    void CallRingGroup()
        {
            if (_configStore.GetFreeAgents().Count > 0)
            {
                CheckAgents();
            }
            else
            {
                _autoResetEvent.WaitOne();
                CheckAgents();
            }
        }
    
  • Hold / Unhold function: since the agents and clients are not transfered to each other - they are communicating through this softphone application -, this softphone has to provide the availability to put the call on hold, and take the call off hold. It can be implemented indirectly; when the client puts the call on hold (and there is an active agent as well), the softphone puts the agent's call on hold. The softphone should be able to handle the case when the client takes the call off hold, as well:
    else if (e.State == CallState.RemoteHeld && _agentCall != null)
        {
            _agentCall.Hold();
        }
    else if (e.State.IsInCall() && _agentCall != null)
        {
            if (_agentCall.CallState == CallState.LocalHeld || _agentCall.CallState == CallState.InactiveHeld)
                _agentCall.Unhold();
        }
    
  • When the call ends: in this case, the client's devices are being disconnected and detached from the call. Through a variable the application indicates if the agent's call needs to be put down, or it's already done (it's already put down, if the agent was the one who hanged up the call). An event is also being invoked about the call's ending.
    else if (e.State.IsCallEnded())
        {
            lock (_sync)
            {
                Console.WriteLine("\nCall has been ended: {0}.", _callInfo.PhoneNumber);
                DestructClientDevices();
    
                if (_needToHangUp && _agentCall != null)
                {
                    _needToHangUp = false;
                    _agentCall.HangUp();
                }
    
                var handler = Completed;
                if (handler != null)
                    handler(this, EventArgs.Empty);
    
                HangUpAgents();
            }
        }
    
    The HangUpAgents() method puts down every agent calls which were created by the CheckAgents() method. The softphone needs to do this, since if the call wasn't answered before it has finished (error, busy etc.), the ringing agent's list contains ring group members, and that list needs to be cleared:
    void HangUpAgents()
        {
            lock (_sync)
            {
                foreach (var checkedAgent in _freeAgentChecks)
                {
                    checkedAgent.HangUp();
                    DestructAgentDevices(checkedAgent);
                }
                _freeAgentChecks.Clear();
            }
        }
    

What happens during the agent's call?

After an agent has accepted the call (after the CheckAgents() method has been called), the application follows the call's state of that agent's call, and is able to do different tasks:

  • Call is being answered: when the call is being answered by an agent, the agent's devices are being attached to the call and connected to the client's devices, and the application also stores the active call in a variable (to separate it from the unanswered agent calls). The agent's phone number is also being removed from the free agents' list, and the list of the agents who were ringed by the softphone, since the HangUpAgents() method would hang up this call as well, if the list would still contain the agent.
    An event notifies the Autodialer class about the call's success, so that can make a new call, if the maximum amount of parallel calls isn't larger than the previously set amount.
    var currentCall = (IPhoneCall)sender;
    
    if (e.State == CallState.Answered)
    {
        lock (_sync)
        {
            SetupAgentDevices(currentCall);
            _agentCall = currentCall;
    
            _configStore.RemoveFreeAgent(currentCall.DialInfo.Dialed);
            _freeAgentChecks.Remove(currentCall);
    
            HangUpAgents();
           
            var handler = ReadyToCall;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }
    }
    
  • Hold / Unhold function: as in the case of the clients' calls, the hold-unhold function needs to be implemented indirectly; the softphone itself should put the client's call on hold, and take that call off hold:
    else if (e.State == CallState.RemoteHeld)
        {
            _call.Hold();
        }
    else if (e.State.IsInCall())
        {
            if (_call.CallState == CallState.LocalHeld || _call.CallState == CallState.InactiveHeld)
                _call.Unhold();
        }
    
  • When the call ends: when an agent's call ends, the amount of currently ringing agents is being decreased by one, and the devices are being disconnected and detached from the call. If this amount reaches 0, the client's call should be hanged up, since there would be noone to connect to. The agent, who was in the call (it can be checked by if the free agents' list contains it or not) has no call anymore should be added to the available agent's list, and the call might needs to be put down (if the client hanged up the call, it shouldn't be hanged up).
    The ReadyToCall event notifies the Autodialer class that the call has been ended, so the next client can be called (if there is more).
    else if (e.State.IsCallEnded())
        {
            lock (_sync)
            {
                _ringingAgents--;
    
                if (!_configStore.GetFreeAgents().Contains(currentCall.DialInfo.Dialed))
                {
                    if (_needToHangUp)
                    {
                        _needToHangUp = false;
                        _call.HangUp();
                    }
                    _configStore.AddFreeAgent(currentCall.DialInfo.Dialed);
                    _autoResetEvent.Set();
                }
                DestructAgentDevices(currentCall);
    
                if (_ringingAgents == 0)
                {
                    HangUpClient();
                    var handler = ReadyToCall;
                    if (handler != null)
                        handler(this, EventArgs.Empty);
                }
            }
        }
    

Please note that, only the mean functions and features of the class were introduced, to learn more please study the source code, and you might also want to understand the simpler Autodialer example as well.

What is Autodialer.cs used for?

The Autodialer class creates the CallHandler instances from CallInfo objects and starts them by calling their Start() method.
The class also listens to the CallHandler class's events, which indicate when a client call has been accepted or ended.

To learn more about the Autodialer class, please visit the Autodialer article.

Conclusion

From this example you could learn how to create predictive autodialer, which is a softphone application and is able to read and process csv files, make calls simultaneously to the destinations, handle a ring group of agents, and send audio data from one call to another through itself by connecting the correct devices.

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

Related Pages

More information