Course 2 / Lecture 6

How to build a VoIP PBX in C#

How to implement Interactive Voice Response using C#?

Ozeki VoIP SIP SDK also makes it possible to create VoIP PBX virtual extensions for your SIP PBX system to be more efficient. Read this guide to get more information about the essential terms and concepts of PBX virtual extensions. You will also learn from the source codes and their explanations how to add virtual extensions to your PBX with Ozeki SIP SDK.

Download: ivr.zip

What is Interactive Voice Response? How to use IVR with PBX?

Interactive voice response (IVR) is a technology that allows a computer to interact with humans through the use of voice and DTMF tones input via keypad.

The virtual PBX extensions are built-in modules of a PBX that are not traditional communication end points but serve some special purposes, for example, voice mail, answering machine functionality or IVR server functionality.

The example program that is introduced in this article is a simple IVR solution that is defined as a virtual extension in a PBX system. As PBX behavior is implemented in the Ozeki VoIP SIP SDK effectively, you only need to define some features and tools for the new virtual extension.

How to implement IVR using C#?

The following program code uses the background support of Ozeki VoIP SIP SDK, therefore you will need to download and install Ozeki SIP SDK on your computer before starting to use the program code. You will also need to have Visual Studio 2010 or compatible IDE and .NET Framework installed on your system, as the program code below is written in C# language.

The example program that is introduced in this article is an extended version of the previous one shown on the VoIP PBX Mediagateway connection page. The following sections will only show the main differences in the two solutions.

This extended example code defines an IVR virtual extension to the PBX system. Code 1 shows the initialization method for the IVR that needs to be implemented in the PBX class. The initialization is done by the method call in the constructor.

The IVR is a virtual extension in the PBX, therefore the initialization process needs to add a new extension to the extension container.

void InitIVR()
{
	var extensionContainer = GetService<IExtensionContainer>();

	var ivr = new IVRExtension("801");
	extensionContainer.TryAddExtension(ivr);
}
Code 1 - IVR virtual extension initialization

When you want to define a new virtual extension to your PBX, you need to create a class derived from the IExtension interface (Code 2).

class IVRExtension : IExtension
Code 2 - The declaration line of the IVR extension class

The constructor of the class is a simple one, it is only used for storing the parameters and creating a handler for the IVR purposes (Code 3).

public IVRExtension(string id)
{
	ExtensionID = id;
	ivrCallHandlers = new List<IVRCallHandler>();
}
Code3 - The constructor of the IVR extension

The IVR extension operates with similar methods as the other extensions. It is needed to create a SIP call object (Code 4) through the PBXFactory class services. The call handler needs to register the call and it needs to be subscribed for the completed event.

public void OnCalled(IPBXCall call, IncomingCallParams callParams)
{
	var ivrCallHandler = new IVRCallHandler(call);
	ivrCallHandlers.Add(ivrCallHandler);
	ivrCallHandler.Completed += HandlerCompleted;
}
Code 4 - Handling a call object in the IVR extension class

The IVR system is based on a menu tree that is initialized in the following method shown in Code 5. The IVR main menu message can be in this example program a written text that is read up by a text to speech converter or a .wav audio file played by the PBX.

The InitializeIVRMenu method creates a text to speech IVR welcoming message and some submenus for demonstrational purposes. The menu element handling is implemented in a separate class that will be introduced in the later sections of this article.

private void InitializeIVRMenu()
{
    rootMenu = new MenuElement("Welcome to OZEKI Interactive Voice Response system. " +
                               "Please press one to enter a test menu. " +
                               "Press two to hear some music. " +
                               "Press three to call someone. " +
                               "Press four to check your voicemail. ", '0');

    rootMenu.AddSubmenu(new MenuElement("This is a submenu, and you pressed key 1. " +
                                        "You can return to the main menu by pressing 0. ", '0'), '1');
    rootMenu.AddSubmenu(new MenuElement(new WaveStreamPlayback(@"Resources\music.wav"), '0'), '2');
    rootMenu.AddSubmenu(new TransferMenuElement("Please enter the phone number and press the pound key when you are finished. " +
                                                "We will transfer the call to the phone number you entered. " +
                                                "You can return to the previous menu with *. ", '*'), '3');
    rootMenu.AddSubmenu(new VoiceMailMenuElement("You will be redirected to check your voice mails. ", '0'), '4');

    rootMenu.Completed += RootMenuCompleted;
}
Code 5 - Creating a sample IVR menu tree

The IVR functionality works with an auto answering process. In the case of an incoming call (Ringing call state), the call is automatically accepted. When the call state changes to InCall, the IVR main menu is executed (Code 6).

private void CallStateChanged(object sender, CallStateChangedArgs e)
{
    switch (e.State)
    {
        case CallState.Ringing:
            pbxCall.Answer();
            break;

        case CallState.InCall:
            rootMenu.Execute(pbxCall);
            break;

        case CallState.Completed:
            OnCompleted();
            break;
    }
}
Code 6 - Call state handling

As it was mentioned before, the IVR tree is built up form menu elements. The menu element data structure is implemented in the MenuElement class. This IVR menu system uses DTMF signal navigation, therefore the menu elements need to specify the DTMF signal that navigates to them.

Code 7 contains one of the menu element constructors that operates with text to speech menu messages. The menu elements are put in a Dictionary where the key is the DTMF signal identifier. This is the easiest way for implementing the navigation. You only need to check if the key exists in the Dictionary and if so, you navigate to the proper menu element specified with that key.

public MenuElement(string greetingMessage, char returnDtmfCommand)
{
    this.greetingMessage = greetingMessage;
    mediaConnector = new MediaConnector();
    textToSpeech = new TextToSpeech();
    phoneCallAudioSender = new PhoneCallAudioSender();
    this.returnDtmfCommand = returnDtmfCommand;

    submenuElements = new Dictionary<char, MenuElement>();
}
Code 7 - IVR menu element constructor for text to speech messages

The IVR menu elements can also operate with recorded .wav audio messages. In these cases the other constructor shown in Code 8 should be used.

The concept behind this solution is the same as in the case of the text to speech version. The menu elements are stored in a Dictionary and the DTMF signals are used as keys for the navigation.

public MenuElement(WaveStreamPlayback wavPlayback, char returnDtmfCommand)
{
    this.wavPlayback = wavPlayback;
    this.returnDtmfCommand = returnDtmfCommand;
    mediaConnector = new MediaConnector();
    phoneCallAudioSender = new PhoneCallAudioSender();

    this.returnDtmfCommand = returnDtmfCommand;
    submenuElements = new Dictionary<char, MenuElement>();
}
Code 8 - IVR menu element constructor for .wav messages

The DTMF navigation is handled in the DtmfReceived method shown in Code 9. When a DTMF signal arrives, the system checks if it is a valid navigation command at the current menu element and if so, the IVR steps to the specified submenu.

The IVR stores a default return command that navigates back to the parent menu of the current element. In the main menu level, the return command ends the call. If the DTMF signal is not the return command, the IVR checks if it is a valid navigation command and if so, the IVR steps into the specified submenu.

protected virtual void DtmfReceived(object sender, VoIPEventArgs<DtmfInfo> e)
{
    char dtmfChar = DtmfNumToChar(e.Item.Signal.Signal);
    if (dtmfChar == returnDtmfCommand)
    {
        OnCompleted();
        return;
    }

    MenuElement submenu;
    submenuElements.TryGetValue(dtmfChar, out submenu);

    if (submenu != null)
    {
        Disable();
        submenu.Completed += OnChildCompleted;
        submenu.Execute(call);
    }
}
Code 9 - DTMF navigation in the IVR extension

When the IVR system is enabled, the following code (Code 10) runs. The application checks if the actual IVR solution works with text to speech or .wav playing and according to that the proper processes start.

protected virtual void Enable()
{
    BackgroundWorker playInvoker = new BackgroundWorker();

    call.DtmfReceived += DtmfReceived;

    if (textToSpeech != null)
    {

        mediaConnector.Connect(textToSpeech, phoneCallAudioSender);
        phoneCallAudioSender.AttachToCall(call);
        playInvoker.DoWork += delegate
                                    {
                                        Thread.Sleep(500);
                                        textToSpeech.AddAndStartText(greetingMessage);
                                    };
    }
    else if (wavPlayback != null)
    {
        mediaConnector.Connect(wavPlayback, phoneCallAudioSender);
        phoneCallAudioSender.AttachToCall(call);
        playInvoker.DoWork += delegate
                                    {
                                        Thread.Sleep(500);
                                        wavPlayback.Start();
                                    };
    }

    playInvoker.RunWorkerAsync();
}
Code 10 - The actual IVR entrance point

The IVR extension is extended in this example program with two functionalities, one for transferring the calls to a human operator and another for a voice mail service. The declaration of the former class is shown in Code 11.

class TransferMenuElement : MenuElement
Code 11 - Transferring to a human operator needs a new menu element class

The transferring menu element overrides the DTMF signal handling method (Code 12). In the transferring menu node, the IVR stores the incoming DTMF signals and when the caller presses "#", the call is transferred to the number specified by the DTMF signal string.

protected override void DtmfReceived(object sender, VoIPEventArgs<DtmfInfo> e)
{
    char dtmfChar = DtmfNumToChar(e.Item.Signal.Signal);
    if (dtmfChar == returnDtmfCommand)
    {
        phoneNumber = string.Empty;
        OnCompleted();
        return;
    }

    if (dtmfChar == '#')
    {
        if (textToSpeech.IsStreaming)
        {
            textToSpeech.Stop();
        }
        call.BlindTransfer(phoneNumber);
        return;
    }

    phoneNumber += dtmfChar;
}
Code 12 - Specification of a telephone number to transfer the call through DTMF signaling

The other special function in the IVR menu is the voice mail service that's implementation is shown in Code 13. The voice mail can be called directly or indirectly as in the case of the example program shown in the Voice Mail extension article.

class VoiceMailMenuElement : MenuElement
{
        public VoiceMailMenuElement(string greetingMessage, char returnDtmfCommand):
            base(greetingMessage, returnDtmfCommand)
        {

        }

        public VoiceMailMenuElement(WaveStreamPlayback wavPlayback, char returnDtmfCommand) :
            base(wavPlayback, returnDtmfCommand)
        {

        }

        protected override void Enable()
        {
            base.Enable();
            textToSpeech.Stopped += TextToSpeechStopped;
        }

        private void TextToSpeechStopped(object sender, EventArgs e)
        {
            call.BlindTransfer(MyPBX.VoiceMailNumber);
        }
}
Code 13 - The voice mail service is attached to the "800" virtual extension

By now you surely understand the basic concepts of the IVR virtual extension implementation. You will be able to define your own solution according to this example program.


Related Pages

More information