Click here to Skip to main content
Click here to Skip to main content

How to build a basic IVR (Interactive Voice Response) menu system with DTMF/voice control in C# to improve your call center

, 23 Jul 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
It explains step-by-step how to develop a basic IVR voice menu system in the most simplest way and explains how to create such professional VoIP functionalities as blind transfer and human speech control.

Introduction

In today’s business world call centers are conquering even more territory than ever. Their main task is serving the customers that is usually done by the sales and technical support departments as product support, telemarketing or market research.

It is essential for good call centers to be able to handle large amount of simultaneous calls, because among large companies hundreds of phone calls might come in simultaneously. Due to this fact, applying effective call managing options (call queuing, call forwarding, call holding, etc.) has become inevitable.

Each well-functioning call center has an Interactive Voice Response (IVR) menu that takes a lot of toll from the agents, because it helps the customers to access to basic services in connection with usually their account details or the products/services available. Usually your callers can access some simple actions through the IVR or you can ask to be forwarded to a live agent. For better understanding, IVRs are voice menu systems that can direct the customers to the menu points they need. It receives the customer’s responses given by their touch-tone telephone keypad entry (DTMF signal). Instead of DTMF signalling, IVR systems can be also controlled with human voice commands (voice control). In this project I will explain the implementation of both of them.

Prerequisites

  1. I have built my IVR application in C#, so you need an IDE (Integrated Development Environment) supporting this programming language, such as Microsoft Visual Studio.
  2. .NET Framework installed on your PC is also needed.
  3. As my IVR system is based on VoIP technology, you need to add some VoIP components to the references in your IDE in order to define the default behaviour of the IVR in the simplest way. Since I have already used Ozeki VoIP SIP SDK for another VoIP development (I have created an IP PBX), I used the prewritten VoIP components of this SDK.

Settings in your IDE (Integrated Development Environment)

Create a new project:

  1. Open Visual Studio, click on File then New project
  2. Select the Visual C# Console Application option
  3. Specify a name for your new project
  4. Click on OK

Add VoIP components to your references to achieve and use the IVR components of the SDK:

  1. Right-click on References
  2. Select the Add references option
  3. Browse the VoIPSDK.dll file that can be found where the SDK has been installed to
  4. Select the .dll and click on the OK button

Writing the code

In my project 3 classes have been used: Softphone.cs, CallHandler.cs; Program.cs. Let’s see the implementation of the 3 classes step-by-step.

Implementing Softphone.cs class

First, you need to create a simple softphone that has the same functions as an ordinary telephone. (It is necessary, because the IVR need to be able to receive calls coming from other telephones. So you will manage the incoming calls with the help of this softphone.) Softphone.cs is the class that is used to introduce how to declare, define and initialize a softphone, how to handle some of the events of the VoIP SDK and how to use its functions. This class will be used in the Program.cs for creating a new softphone.

As a first step, add some extra using lines (Code example 1):

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

Code example 1: Add some new using lines

Now you need to create a softphone and a phone line from the ISoftPhone and IPhoneLine interfaces (Code example 2):

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

Code example 2: IsoftPhone and IPhoneLine objects

In a constructor, you also need to initialize this softphone with default parameters (Code example 3). You need to set the port range, indicated by the first paramerter as the minPortRange and the maximum parameter maxPortRange. This is the port's interval. The third parameter is the listening port (5060 is the port of the SIP). By subscribing to the IncomingCall event, incoming calls will be monitored continuously.

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

Code example 3: Initialization of the softphone

Now you need to register your SIP Account to the server by using the Register method (Code example 4). This method gets all of the values required for the registration as parameters: registrationRequired, displayName, userName, authenticationId, registerPassword, domainHost and domainPort. These parameters will be used to create a SIP account with the constructor of the SIPAccount class. After creating the account, the method configures the Network Address Translation (NAT) with the NatTraversalMethod in order to ensure incoming calls will get through the firewall. Having done these steps, the system can create the phone line (CreatePhoneLine method) with the help of the account and the NatConfiguration allowing the IVR can be called. Finally, you need to register this phone line.

public void Register(bool registrationRequired, string displayName, string userName, string authenticationId, string registerPassword, string domainHost, int domainPort)  
{  
    try  
    {  
       var account = new SIPAccount(registrationRequired, displayName, userName, authenticationId, registerPassword, domainHost, domainPort);  
       Console.WriteLine("\n Creating SIP account {0}", account);  
       var natConfiguration = new NatConfiguration(NatTraversalMethod.None);  
  
       phoneLine = softphone.CreatePhoneLine(account, natConfiguration);  
       Console.WriteLine("Phoneline created.");  
  
       phoneLine.PhoneLineStateChanged += phoneLine_PhoneLineStateChanged;  
  
       softphone.RegisterPhoneLine(phoneLine);  
    }  
    catch(Exception ex)  
    {  
       Console.WriteLine("Error during SIP registration" + ex.ToString());  
    }  
}  

Code example 4: SIP account registration

Implementing CallHandler.cs class

Now, let’s see the second class. CallHandler.cs is used to manage the incoming calls. In case of an inbound call, the caller will hear a greeting message through the speaker and after listening the selectable menu items, the caller can choose one of the menu items by pressing a button on his/her keypad.

In order to manage the incoming calls you need to create some objects: mediaConnector, audioHandler, phoneCallAudioSender and greetingMessageTimer from the ICall interface and the MediaConnector, AudioHandler, PhoneCallAudioSender and Timer classes (Code example 5).

ICall call;  
MediaConnector mediaConnector;  
AudioHandler audioHandler;  
PhoneCallAudioSender phoneCallAudioSender;  
Timer greetingMessageTimer; 

Code example 5: Add some new objects

The constructor of the class gets an ICall type parameter and it makes the basic setups for the class. It sets the interval of the greetingMessageTimer (30 seconds) that is for repeating the greeting message on the main menu level and it attaches the phoneCallAudioSender to the call (Code example 6).

public CallHandler(ICall call)  
{  
      greetingMessageTimer = new Timer();  
      greetingMessageTimer.Interval = 30000;  
      greetingMessageTimer.Elapsed += greetingMessageTimer_Elapsed;  
      this.call = call;  
      phoneCallAudioSender = new PhoneCallAudioSender();  
      phoneCallAudioSender.AttachToCall(call);  
      mediaConnector = new MediaConnector();  
} 

Code example 6: Add some new objects

Now take a look at the methods that are code blocks containing a series of statements.

One of the most important methods of this class is the Start() method. It will be called by the main method. In this method you can subscribe to the CallStateChanged and DtmfReceived events then accept the call (Code example 7).

  • The call state change event is one of the most important events. It informs both the server and the client about the changes of the call state.
  • The DTMF received event informs the server about the button pressed by the client. It indicates that he/she want to step to another menu level.
public void Start()  
{  
    call.CallStateChanged += call_CallStateChanged;  
    call.DtmfReceived += call_DtmfReceived;  
    call.Accept();  
}  

Code example 7: Creating the Start() method

In the Start method you are subscribed to the DtmfReceived event using the call_DtmfReceived() method and you can set what happens when a caller press a DTMF button. It can be seen in the Code example 8 that - in this example - if the caller presses 1, he/she can hear some product information with the help of the constructor of TextToSpeech class. By pressing 2, the caller can hear a sample mp3 song by calling the Mp3ToSpeaker method.

void call_DtmfReceived(object sender, VoIPEventArgs<DtmfInfo> e)  
{  
    DisposeCurrentHandler();  
    switch (e.Item.Signal.Signal)  
    {  
         case 0: break;  
         case 1: TextToSpeech("Product XY has been designed for those software developers who especially interested in VoIP developments. If you prefer .NET programming languages, you might be interested in Product XY."); break;  
         case 2: MP3ToSpeaker(); break;  
    }  
}   

Code example 8: What happens if the caller presses the DTMF buttons

1.) TextToSpeech method

Using the TextToSpeech method you can add a text message what you want to play to your caller. It will be read by the TextToSpeech engine. Just make a TextToSpeech object, connect it to the phoneCallAudioSender by using the Media Connector and call the AddAndStartText method (Code example 9).

private void TextToSpeech(string text)  
{  
    var tts = new TextToSpeech();  
    audioHandler = tts;  
  
    mediaConnector.Connect(audioHandler, phoneCallAudioSender);  
    tts.AddAndStartText(text);  
}   

Code example 9: The TextToSpeech method

2.) MP3ToSpeaker method

Using the functions of the MP3ToSpeaker method, the IVR can easily play an MP3 file to the caller (e.g. a greeting message). You only need to create an MP3StreamPlayback object with the file path parameter, connect it to the PhoneCallAudioSender via the mediaConnector then start the streaming (StartStreaming() method) (Code example 10).

private void MP3ToSpeaker()  
{  
    var mp3Player = new MP3StreamPlayback("../../test.mp3");  
    audioHandler = mp3Player;  
  
    mediaConnector.Connect(mp3Player, phoneCallAudioSender);  
    mp3Player.StartStreaming();  
} 

Code example 10: The MP3ToSpeaker method

Implementing Program.cs class

You arrived to the last class that needs to be implemented. Program.cs introduces the usage of the softphone and the callHandler objects, and handles the console and DTMF events coming from the caller.

First, in the Main section, you need to create the softphone object in order to reach the methods created in the Softphone class. (Then there will be some instructions about the code and the call of the sipAccountInitialization method.) You can add your SIP account values in this section. Now you need to subscribe to the IncomingCall event in order to manage the incoming calls (Code example 11).

static void Main(string[] args)  
{  
      callHandlers = new List<CallHandler>();  
      var softphone = new Softphone();  
  
      Console.WriteLine("/* Program usage description */");  
  
      sipAccountInitialization(softphone);  
  
      softphone.IncomigCall += softphone_IncomigCall;  
      Console.ReadLine();  
}  

Code example 11: The Main() method

Due to the CallHandler list, this IVR is able to manage multiple calls. When a call comes in, the system accepts that automatically and then the call will be added to the list. Each further incoming call will be added to the list. The list contains the calls as long as they are active. When a call is ended, it is removed from the CallHandler list.

The sipAccountInitialization() method is responsible for getting the SIP account components from the user. As you could see above, the SIPAccount constructor needs the following values:

  • Authentication ID
  • User name (default is the authentication ID)
  • Display name (default is the authentication ID)
  • Password
  • Domain Host (default is the local host)
  • Domain Port (default is 5060)

So you need to add these values for the program and it will call the Register method of the Softphone class by using them (Code example 12).

private static void sipAccountInitialization(Softphone softphone)  
{  
     Console.WriteLine("Please setup your SIP account!\n");  
     Console.WriteLine("Please set your authentication ID: ");  
     var authenticationId = Read("authenticationId", true);  
  
     Console.WriteLine("Please set your user name (default:" +authenticationId +"): ");  
     var userName = Read("userName", false);  
     if (string.IsNullOrEmpty(userName))  
             userName = authenticationId;  
  
     Console.WriteLine("Please set your name to be displayed (default: " +authenticationId +"): ");  
     var displayName = Read("displayName", false);  
     if (string.IsNullOrEmpty(displayName))  
             displayName = authenticationId;  
  
     Console.WriteLine("Please set your registration password: ");  
     var registrationPassword = Read("registrationPassword", true);  
  
     Console.WriteLine("Please set the domain name (default: your local host): ");  
     var domainHost = Read("domainHost", false);  
     if (string.IsNullOrEmpty(domainHost))  
             domainHost = NetworkAddressHelper.GetLocalIP().ToString();  
     Console.WriteLine(domainHost);  
  
     Console.WriteLine("Please set the port number (default: 5060): ");  
     int domainPort;  
     string port = Read("domainPort", false);  
     if (string.IsNullOrEmpty(port))  
     {  
           domainPort = 5060;  
     }  
     else  
     {  
           domainPort = Int32.Parse(port);  
     }  
     Console.WriteLine("\nCreating SIP account and trying to register...\n");  
     softphone.Register(true, displayName, userName, authenticationId, registrationPassword, domainHost, domainPort);  
}   

Code example 12: The sipAccountInitialization() method

The Read() function has 2 parameters: inputname and readWhileEmpty. If the readWhileEmpty is false, there is a default value of this component (for example 5060 for the Domain Port). In this case the input can be empty or null. Else you need to add an input for the system and the function will return with the input to the Main method (Code example 13).

private static string Read(string inputName, bool readWhileEmpty)  
{  
     while (true)  
     {  
          string input = Console.ReadLine();  
  
          if (!readWhileEmpty)  
          {  
               return input;  
          }  
  
          if (!string.IsNullOrEmpty(input))  
          {  
               return input;  
          }  
  
          Console.WriteLine(inputName +" cannot be empty!");  
          Console.WriteLine(inputName +": ");  
    }  
}   

Code example 13: The Read() function

In order to be notified in case of an incoming call, you need to set an event specifically for this purpose. This is the IncomingCall event. If there is a notification about a call awaiting to be accepted, the call has to be accepted by the IVR system. In softphone_IncomingCall() method there is a callHandler object for managing the methods of the CallHandler class. If there is an incoming call, call Start() method of the CallHandler (Code example 14).

static void softphone_IncomigCall(object sender, Ozeki.VoIP.VoIPEventArgs<Ozeki.VoIP.IPhoneCall> e)  
{  
     Console.WriteLine("Incoming call!");  
     var callHandler = new CallHandler(e.Item);  
     callHandler.Completed += callHandler_Completed;  
  
     lock (callHandlers)  
           callHandlers.Add(callHandler);  
  
     callHandler.Start();  
}

Code example 14: The softphone_IncomingCall() method

The implementation of the blind transfer

Now it is time to see how to optimize your IVR with blind transfer functionality.

The call transfer can be done automatically by a call center server application or it can be coordinated by a human operator. In case of a blind transfer the first option is the most usual. Blind transfer means that the call will be transferred to a randomly chosen end-point, basically the first available agent.

If the user selects this option, the system transfers the call to another phone number automatically. For this, you need to add some modifications to this basic IVR code.

At the beginning of the Program class make static string variable for storing the blind transfer value, then the user need to enter a phone number if he/she would like to use the blind transferring function. If he/she do not want to use this option, then press '0' (Code example 15).

Console.WriteLine("Please set the number for blind transferring! If you don't want to use this function of the IVR, press 0!");
blindTransfer = Read("blindTransfer", true); 

Code example 15: Making static string variable

After that, you need to pass this value to the CallHandler class. You can do it by calling a new (for example blindTransferNumber() ) method in the softphone_IncomingCall section of your code. By using this you can give the blindTransfer value to the CallHandler class (Code example 16).

public void BlindTransferNumber(string blindTransfer)
{
            blindTransferNumber = blindTransfer;
} 

Code example 16: The blindTransferNumber() method

The only thing you left to do is adding a new case to the switch statement in the call_DtmfReceived() method. If the user press 3 and if the number that he/she provided was zero in the program class, then write on the console that he/she can not use this function of the IVR. Else call the blindtransfer method and give the blindtransfer number value to it as a parameter (Code example 17).

case 3:
                    {
                        if (blindTransferNumber == "0")
                        {
                            TextToSpeech("You did not add any number for blind transferring!");
                            break;
                        }
                        else
                        {
                            call.BlindTransfer(blindTransferNumber);
                            break;
                        }
                    } 

Code example 17: Adding a new case to the switch statement in the call_DtmfReceived() method

The implementation of the voice-controlled IVR

To make the IVR menu system more effective you can facilitate your callers’ job with voice control. It means that they can navigate between the menu items using human voice commands instead of pressing any DTMF buttons.

In order to implement the human speech based control, you need to modify the CallHandler class and create the events and methods of the SpeechToText abstract class.

As you can see above, the CallHandler.cs is used to manage the incoming calls. To implement the voice control functionality, first you need to create some new objects (Code example 18):

  • PhoneCallAudioReceiver: to receive audio from the caller
  • IEnumerable: to give word options to the system to be able to recognize them
  • SpeechToText: to convert the human speech into text format
PhoneCallAudioReceiver phoneCallAudioReceiver;  
IEnumerable<string> choices;  
SpeechToText stt;

Code example 18: Add some new objects in the CallHandler.cs class

Code example 19 shows the CallHandler() constructor, where you need to set the created objects and make instances for them. The PhoneCallAudioReceiver object should be atttached to the call. Furthermore, you need to create an instance for the SpeechToText object by using the CreateInstance method of the SpeechToText class. In order to allow the system to recognize the voice commands pronounced by the caller, you need to add some words to the choices list.

public CallHandler(ICall call)  
{  
      greetingMessageTimer = new Timer();  
      greetingMessageTimer.Interval = 30000;  
      greetingMessageTimer.Elapsed += greetingMessageTimer_Elapsed;  
      this.call = call;  
      phoneCallAudioSender = new PhoneCallAudioSender();  
      phoneCallAudioSender.AttachToCall(call);  
      mediaConnector = new MediaConnector();  
      phoneCallAudioReceiver = new PhoneCallAudioReceiver();  
      phoneCallAudioReceiver.AttachToCall(call);  
      choices = new List<string>() { "first", "second"};  
      stt = SpeechToText.CreateInstance(choices);  
}

Code example 19: The CallHandler() constructor

The Start() method can be seen in Code example 20. In this section you need to connect the PhoneCallAudioReceiver to the stt by using the mediaConnector. As a result, the system will be able to start the recognition of the human speech. You also need to subscribe to the WordHypothesized event of the SpeechToText class.

public void Start()  
{  
     mediaConnector.Connect(phoneCallAudioReceiver, stt);  
     call.CallStateChanged += call_CallStateChanged;  
     stt.WordHypothesized += CallHandler_WordHypothesized;  
     call.Accept();  
}

Code example 20: The Start() method

When the system detects the human speech and the recognized word is equal to one word of the choices list, the CallHandler_WordHypothesized() method will be started. This method manages the switch statement. As you can see below, if the caller says ’first’, a brief product information can be heard with the help of the constructor of TextToSpeech class. If he/she says ’second’, the caller can hear a sample mp3 song by calling the Mp3ToSpeaker method.

void CallHandler_WordHypothesized(object sender, SpeechDetectionEventArgs e)  
{  
      DisposeCurrentHandler();  
      Console.WriteLine(e.Word.ToString());  
      switch (e.Word.ToString())  
      {  
           case "first": TextToSpeech("Product XY has been designed for those software developers who especially interested in VoIP developments. If you prefer .NET programming languages, you might be interested in Product XY."); break;  
           case "second": MP3ToSpeaker(); break;  
      }  
}

Code example 21: The CallHandler_WordHypothesized() method

How to create a multi-level IVR

Concerning to the fact, that in today’s business world the more advanced multi-level IVRs are commonly used, I improved my IVR solution. I created an advanced menu system that can navigate the caller through more than one menu level.

As this article demonstrates how to build a basic IVR, I though it would be better to present my improvement as tip. If you are interested in developing a multi-level IVR menyu sysem, please study my tip that explains its implementation step-by step:

How to create a multi-level IVR (Interactive Voice Response) menu system in C#: http://www.codeproject.com/Tips/752443/How-to-create-a-multi-level-IVR-Interactive-Voice

Summary

To sum it up, a call center can be really effective if it is able to handle a large amount of simultaneous calls as well as it has advanced call management features. In my project I have developed a basic IVR that can receive and manage the incoming calls without human intervention. By building blind transfer functionality, my IVR can be used to transfer the caller to a live operator – automatically by using DTMF signaling. You can extend your IVR – and therefore improve your call center – with further more professional features of course, such as call queue, voicemail and call recording, etc.

References

Theoretical background:

Download the necessary software:

Supplementary information:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

W.Walker

United Kingdom United Kingdom
No Biography provided

Comments and Discussions

 
Questioni Have a question PinprofessionalSanthoshobject27-Mar-14 5:55 
AnswerRe: i Have a question PinmemberW.Walker27-Mar-14 21:18 
AnswerRe: i Have a question PinmemberW.Walker27-Mar-14 22:41 
QuestionReally interesting but can u help me fix this? [modified] Pinmemberrtz8726-Mar-14 10:02 
AnswerRe: Really interesting but can u help me fix this? PinmemberW.Walker26-Mar-14 21:44 
QuestionIVR with Multiple Lines PinmemberDaddyO5021-Mar-14 12:08 
AnswerRe: IVR with Multiple Lines PinmemberW.Walker23-Mar-14 23:59 
Due to the CallHandler list, this IVR is able to manage multiple calls.
 
When a call comes in, the system accepts that automatically and then the call will be added to the list. Each further incoming call will be added to the list. The list contains the calls as long as they are active. When a call is ended, it is removed from the CallHandler list.
 
To test this functionality, just download and install my IVR project and make at least 2 calls simultaneously by using two VoIP phones (e.g. two softphones). (For this, you can use Ozeki's demo softphone[^].)
QuestionMy Vote of 5* PinmemberMas1119-Mar-14 23:43 
AnswerRe: My Vote of 5* PinmemberW.Walker20-Mar-14 0:21 
QuestionImproving Call Centre PinprotectorDaveAuld19-Mar-14 10:39 
AnswerRe: Improving Call Centre PinmemberW.Walker19-Mar-14 21:33 
GeneralRe: Improving Call Centre PinprotectorDaveAuld20-Mar-14 22:58 
GeneralRe: Improving Call Centre PinmemberW.Walker21-Mar-14 1:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 23 Jul 2014
Article Copyright 2014 by W.Walker
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid