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

How to improve your VoIP PBX with voicemail functionality in C#

, 8 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
This guide demonstrates how to build voicemail service into your VoIP PBX in C# in order to manage your calls more effectively.

Introduction

Accepting the the partners’ incoming calls has a major significance is business world. However, there are many cases when you are unable to accept your customers’ calls: for instance if you are out of the office, you are on holiday or it is weekend. Voicemail systems allow your customers to leave a message for your company by recording their voice, then later one of the employees can listen the recordings back.

Background

Two months ago I have created a simple SIP PBX that can be used as a corporate phone system. At the end of the article I have mentioned that I was working on a voicemail solution in order to improve my PBX. In this brief article I would like to describe this voicemail project while explaining how to build a voicemail feature into your existing PBX.

Prerequisites

This voicemail project is based on my previously implemented PBX development. (This is an additional improvement, so my original PBX is viable without this feature, but at the same time it can be more professional completed with voicemail service.) For this reason, as a first step, please study my previous PBX tutorial. It contains all the initial steps you need to do for starting this project. This article can be found here:

How to build a simple SIP PBX in C# extended with dial plan functionality: http://www.codeproject.com/Articles/739075/How-to-build-a-simple-SIP-PBX-in-Csharp-extended-w

  • My PBX has been written in C#, so you need a development environment supporting this language, e.g. Microsoft Visual Studio.
  • .NET Framework installed on your PC is also needed.
  • For defining the default PBX behaviour, you need to add some VoIP components to the references in Visual Studio. As I have already used Ozeki VoIP SIP SDK for more VoIP developments, I used the background support of this SDK. So it needs to be installed on your PC, as well.

Writing the code

After implementing the PBX, you have a phone system with dial plan functionality. To create a voicemail service, first you need a virtual extension that requires some modifications in the original PBX code.

Modifications in the PBX class

As you can see in Code example 1, the new constructor of the PBX class contains an extension container. This tool is resposible for the management of the virtual extension. Due to the extension handler you can create any virtual extensions using a specific SIP account.

protected override void OnStart()  
{  
    Console.WriteLine("PBX started.");  
  
    var callManager = GetService<ICallManager>();  
    callManager.SetDialplanProvider(new MyDialplanProvider(userInfoContainer));  
    pbxCallFactory = GetService<IPBXCallFactory>();  
  
  
    var extensionContainer = GetService<IExtensionContainer>();  
  
    var rootPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);  
    var voiceMailRootPath = Path.Combine(rootPath, "Records");  
    extensionContainer.TryAddExtension(new VoiceMailExtension(new Account("800", "800", localAddress), pbxCallFactory, voiceMailRootPath));  
  
    SetListenPort(TransportType.Udp, 5060);  
    Console.WriteLine("Listened port: 5060(UDP)");  
    base.OnStart();  
} 

Code example 1: The new constructor of the PBX class

Modifications in the dial plan provider class

This step is needed because the dial plan rules should contain the cases when the voicemail extension is called (Code example 2). The voicemail will answer a call in two cases:

  1. The first one is when an extension does not answer a call. In this case the PBX redirects the call to the voicemail extension. This extension will answer the call and record the caller’s voice message automatically.
  2. The other case occurs when an endpoint calls the voicemail extension directly in order to listen to the recorded voicemails back.
public RoutingRule GetRoutingRule(ISIPCall caller, DialInfo callee, SingleRoutingRule.CalleeState calleeState)  
{  
  
    if (calleeState != SingleRoutingRule.CalleeState.Calling) //busy or notfound  
    {  
        if (callee.UserName == "800" && calleeState != SingleRoutingRule.CalleeState.Calling) // VoiceMail is not available  
            return null;  
  
        UserInfo userInfo;  
        if (userInfoContainer.TryGetUserInfo(callee.UserName, out userInfo))  
            return new SingleRoutingRule("800", caller.Owner.Account.UserName, callee.UserName);  
        return null;  
    }  

Code example 2: Modifications in the dial plan provider class

The implementation of the voicemail extension

Concerning to the fact that the voicemail is a virtual extension, it needs to be defined in a class that implements the IExtension interface. The voicemail extension should be able to handle the contact information while the system needs to know which extension wants to listen the messages back (Code example 3).

class VoiceMailExtension : IExtension  
{  
        List<VoiceMailCallHandler> voiceMailCallHandlers;  
        IPBXCallFactory pbxCallFactory;  
        string voiceMailRootPath;  
  
        public VoiceMailExtension(Account account, IPBXCallFactory pbxCallFactory, string voiceMailRootPath)  
        {  
            ContactInfo = new ContactInfo();  
            Account = account;  
            this.pbxCallFactory = pbxCallFactory;  
            this.voiceMailRootPath = voiceMailRootPath;  
  
            voiceMailCallHandlers = new List<VoiceMailCallHandler>();  
        }    

Code example 3: The definition of the virtual extension

Now there is a need for pbxCall objects that handle the calls of the particular extensions. Code example 4 shows the implementation of the pbxCall object.

public ISIPCall CreateCall()  
{  
    var pbxCall = pbxCallFactory.CreateIncomingPBXCall(this, SRTPMode.None);  
    var voiceMailCallHandler = new VoiceMailCallHandler(pbxCall, Account.UserName, voiceMailRootPath);  
  
    voiceMailCallHandlers.Add(voiceMailCallHandler);  
    voiceMailCallHandler.Completed += HandlerCompleted;  
  
    return pbxCall;  
}  

Code example 4: Creating the pbxCall object

As you can see on Code example 5, the current voicemail calls are handled in a separate classes. Therefore, the calls should be subscribed for the call state changed event (it is implemented in the constructor of the class).

class VoiceMailCallHandler  
{  
        IPBXCall pbxCall;  
        string extensionUserName;  
        IVoiceMailCommand voiceMailCommand;  
        string voiceMailRootPath;  
  
        public VoiceMailCallHandler(IPBXCall pbxCall, string extensionUserName, string voiceMailRootPath)  
        {  
            this.extensionUserName = extensionUserName;  
            this.voiceMailRootPath = voiceMailRootPath;  
  
            this.pbxCall = pbxCall;  
            ((ICall)this.pbxCall).CallStateChanged += call_CallStateChanged;  
        } 

Code example 5: Handling of the voicemail calls

In Code example 6 you can see how to implement the event handler method of the call state change. This is necessary in two different cases when there is an incoming call:

  1. The first one is when the caller calls the phone number of the voicemail extension directly. In this case the voicemail will be played for listening. This is implemented in the VoiceMailPlayCommand class.
  2. When the caller called an extension that did not answer and the call was redirected to the voicemail extension, the caller can leave a message by using the VoiceMailRecordCommand class functionalities.

In case of any problems (such as missing caller or callee data) the call is rejected.

void call_CallStateChanged(object sender, VoIPEventArgs<CallState> e)  
{  
    if (e.Item == CallState.Ringing)  
    {  
        if(pbxCall.CustomTo == null || pbxCall.CustomFrom == null)  
        {  
            pbxCall.Reject();  
            return;  
        }  
  
        if (pbxCall.CustomTo == extensionUserName)  
        {  
            voiceMailCommand = new VoiceMailPlayCommand(pbxCall, voiceMailRootPath);  
            voiceMailCommand.Completed += ActionCompleted;  
        }  
        else  
        {  
            voiceMailCommand = new VoiceMailRecordCommand(pbxCall, voiceMailRootPath);  
            voiceMailCommand.Completed += ActionCompleted;  
        }  
  
        voiceMailCommand.Execute();  
    }  
}  

Code example 6: How to implement the event handler method of the call state change

As Code example 7 shows, the behaviour of the voicemail is implemented in two separate classes, both of them derived from IVoiceMailCommand.

class VoiceMailPlayCommand : IVoiceMailCommand  

Code example 7: The voicemail behaviour

When you listen to your voicemails back, the playback starts with a text-to-speech message that informs you about the number of messages to be listened. This text-to-speech process is specified by the constructor of this class. Any other features are defined in other methods (Code example 8).
public VoiceMailPlayCommand(IPBXCall call, string voiceMailRootPath)  
{  
    extensionVoiceMailPath = Path.Combine(voiceMailRootPath, call.CustomFrom);  
  
    messageFormat = "You have {0} new message";  
  
    this.call = call;  
  
    mediaConnector = new MediaConnector();  
    textToSpeech = new TextToSpeech();  
    phoneCallAudioSender = new PhoneCallAudioSender();  
    phoneCallAudioSender.AttachToCall(call);  
  
    mediaConnector.Connect(textToSpeech, phoneCallAudioSender);  
  
    this.call.CallStateChanged += CallStateChanged;  
    textToSpeech.Stopped += TextToSpeechCompleted;  
} 
Code example 8: Text-to-speech process at the beginning of the playback

If there is an incoming call during the listening, it will be accepted automatically by the voicemail extension (Code example 9).

public void Execute()  
{  
    call.Accept();  
}  

Code example 9: Automated call accepting during the message listening

When the call has accepted, the state of the call changes to InCall and the introduction message will be read automatically by the text-to-speech engine (Code example 10).

void CallStateChanged(object sender, VoIPEventArgs<CallState> e)  
{  
    if (e.Item.IsInCall())  
        textToSpeech.AddAndStartText(string.Format(messageFormat, GetVoiceMails().Count));  
    else if (e.Item.IsCallEnded())  
        CleanUp();  
}  

Code example 10: After accepting the call, the introduction message will be read automatically

The text-to-speech object should be subscribed for the Completed event. The constructor completes this. When the introduction messgae reading has done, the Completed event occurs and the event handler will be invoked. After reading the number of messages to be heard, the voicemail extension starts to play the first message. If there are no more messages waiting, the call is finished (Code example 11).

void TextToSpeechCompleted(object sender, EventArgs e)  
{  
    var voiceMails = GetVoiceMails();  
  
    if (voiceMails.Count == 0)  
        call.HangUp();  
    else  
        PlayVoiceMail();  
} 

Code example 11: The voicemail extension starts to play the first message waiting

Replaying the voicemails is a recursive process. You can see on Code example 12 that if there is at least one message left that will be played and then the played message will be deleted.

void TextToSpeechCompleted(object sender, EventArgs e)  
{  
    var voiceMails = GetVoiceMails();  
  
    if (voiceMails.Count == 0)  
        call.HangUp();  
    else  
        PlayVoiceMail();  
} 

Code example 12: Replaying the voicemails is a recursive process

Code example 13 contains the code that is called when the voicemail extension deletes a played message. This method chooses the file that was set as the previous current message and deletes it.

void DeletePlayedVoiceMail()  
{  
    try  
    {  
        if (currentVoiceMailPath == null)  
            return;  
  
        if(waveStreamPlayback == null)  
            return;  
  
        mediaConnector.Disconnect(waveStreamPlayback, phoneCallAudioSender);  
        waveStreamPlayback.Dispose();  
  
        File.Delete(currentVoiceMailPath);  
    }  
    catch (Exception ex)  
    {  
        Console.WriteLine(ex.Message);  
    }  
}   

Code example 13: Replaying the voicemails is a recursive process

Now let’s see the VoiceMailRecordCommand class (Code example 14) that is used when the incoming call has been redirected to the voicemail extension because the called party didn’t accept that. The VoiceMailRecordCommand class also implements the IVoiceMailCommand interface and automatically accepts the call. As it can be seen below, the constructor initializes a text-to-speech reader that reads out a simple informative message to the caller.

public VoiceMailRecordCommand(IPBXCall call, string voiceMailRootPath)  
{  
    extensionVoiceMailPath = Path.Combine(voiceMailRootPath, call.CustomTo);  
    mediaConnector = new MediaConnector();  
  
    phoneCallAudioReceiver = new PhoneCallAudioReceiver();  
    phoneCallAudioReceiver.AttachToCall(call);  
  
    phoneCallAudioSender = new PhoneCallAudioSender();  
    phoneCallAudioSender.AttachToCall(call);  
  
    textToSpeech = new TextToSpeech();  
    textToSpeech.AddText(string.Format("{0} is not available. Please leave a message after the beep.", call.CustomTo));  
  
    mediaConnector.Connect(textToSpeech, phoneCallAudioSender);  
  
  
    this.call = call;  
    this.call.CallStateChanged += CallStateChanged;  
    textToSpeech.Stopped += TextToSpeechCompleted;  
}  

Code example 14: The constructor of the VoiceMailRecordCommand class

The ’beep’ is played after the informative message read out by the text-to-speech engine. This sound can be implemented by using DTMF signalling (Code example 15).

private void SendBeep()  
{  
    call.StartDTMFSignal(VoIPMediaType.Audio, DtmfNamedEvents.Dtmf0);  
    Thread.Sleep(500);  
    call.StopDTMFSignal(VoIPMediaType.Audio, DtmfNamedEvents.Dtmf0);  
}  

Code example 15: The implementation of ’beep’

Code example 16 shows that the voicemail recorder accepts the call and plays the informative message and the ’beep’ automatically. This feature is implemented in the following event handler methods: CallStateChange and TextToSpeechCompleted.

void CallStateChanged(object sender, VoIPEventArgs<CallState> e)  
{  
    if (e.Item.IsInCall())  
        textToSpeech.StartStreaming();  
    if(e.Item.IsCallEnded())  
        CleanUp();  
}  
  
void TextToSpeechCompleted(object sender, EventArgs e)  
{  
    SendBeep();  
    RecordVoiceMail();  
} 

Code example 16: The CallStateChange and TextToSpeechCompleted event handler methods

And finally, take a look at the recorded files. The current voicemail recording is implemented in the RecordVoiceMail method that records the messages in .wav format by using the WaveStreamRecorder tool. All the recorded audio files will be stored in that folder by the name of the called extension. The file name contains the name of the caller (Code example 17).

private void RecordVoiceMail()  
{  
    if(!Directory.Exists(extensionVoiceMailPath))  
        Directory.CreateDirectory(extensionVoiceMailPath);  
  
    var currentDate = DateTime.Now.ToString("yyyy_dd_mm_hh_ss");  
  
    var fileName = string.Format("{0}_{1}.wav", call.DialInfo.UserName, currentDate);  
    var filePath = Path.Combine(extensionVoiceMailPath, fileName);  
  
    waveStreamRecorder = new WaveStreamRecorder(filePath);  
    mediaConnector.Connect(phoneCallAudioReceiver, waveStreamRecorder);  
  
    waveStreamRecorder.StartStreaming();  
} 

Code example 17: Handling the recorded files

Summary

To sum it up, extending your existing VoIP PBX with specific features can be really simple by using a VoIP SDK (and its VoIP components of course). Voicemail – however this is an great call management option – is not the only one extra feature that can be built into your PBX. If you study my tip above and the referenced additional articles and tutorials, you can easily develop more advanced functionalities (such as call queue or conference room, etc.) based on your specific needs.

References

Useful source (initial development tasks):

  • This voicemail solution is based on my previously created PBX project. To achieve the initial tasks related to the PBX development, please study the following article:
How to build a simple SIP PBX in C# extended with dial plan functionality: http://www.codeproject.com/Articles/739075/How-to-build-a-simple-SIP-PBX-in-Csharp-extended-w

Theoretical background:

Download the required software:

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

 
QuestionCost of Ozeki library? PinmemberGustav Brock12-May-14 0:26 
AnswerRe: Cost of Ozeki library? PinmemberGustav Brock12-May-14 0:30 
GeneralRe: Cost of Ozeki library? PinmemberW.Walker12-May-14 2:23 
GeneralMy vote of 5 PinmemberHumayun Kabir Mamun8-May-14 2:02 
GeneralRe: My vote of 5 PinmemberW.Walker8-May-14 2:45 
QuestionNice! PinprofessionalVolynsky Alex7-May-14 22:55 
AnswerRe: Nice! PinmemberW.Walker7-May-14 23:22 
GeneralRe: Nice! PinprofessionalVolynsky Alex8-May-14 3:18 

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
Web03 | 2.8.141223.1 | Last Updated 8 May 2014
Article Copyright 2014 by W.Walker
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid