Course 1 / Lecture 7

How to handle more than one call in the same time

Download: parallel-calls.zip

This tutorial explains how to build a C# softphone with Ozeki VoIP SIP SDK that handles more than one call at the same time. You will learn how each incoming call gets its own independent handler with its own media stream, so accepting a second call never disrupts a call already in progress. The example builds on the previous "Making and accepting calls" tutorial, adding a per-call handling model on top of basic call acceptance. Follow the steps below to build the project, run it, and place overlapping calls to see concurrent handling in action.

Handling simultaneous calls poster image

Why do we need to handle simultaneous calls?

A softphone that can only manage one call at a time falls over the moment a second caller reaches it while the first call is still active, either dropping the new call or blocking it entirely. Real deployments such as IVR lines, call queues, or shared extensions routinely receive several inbound calls within the same short window, so each one needs its own independent state, media stream, and lifecycle. Without a per-call handling model, one caller's actions (going on hold, hanging up) could accidentally interfere with a completely unrelated call happening at the same time.

Why do we need to handle simultaneous calls
Why do we need to handle simultaneous calls

What is concurrent call handling?

Concurrent call handling means the softphone can accept and manage several calls at once, with each call's state tracked independently of the others. In this example, every incoming call is wrapped in its own CallHandler instance, which owns that call's media objects and event subscriptions, so hanging up or finishing one caller's audio has no effect on any other active call. The calls are kept in a shared list purely so the application can track and clean them up, not to coordinate their behavior.

What is concurrent call handling
What is concurrent call handling

How does it work?

Each time the softphone raises its IncomingCall event, the program wraps that call in a new CallHandler and adds it to a shared list. The handler answers the call, attaches its own MP3StreamPlayback and PhoneCallAudioSender, and starts playing audio the moment the call is answered. When the mp3 finishes or the call ends, the handler hangs up, disposes its own media objects, and removes itself from the list, leaving every other active call completely untouched.

How simultaneous call handling works
How does it work

How to build and run your solution

Download the project, extract the archive, open the solution in Visual Studio, then build and run the console application. Complete these steps before continuing to the testing section, where you will configure the SIP account and view incoming calls.

How to test the solution

Enter your SIP account details and wait for the application to register successfully. Once registered, place two calls to the softphone in quick succession while the first call is still active, and confirm both callers hear the sample mp3 independently. End one call and verify the other keeps running undisturbed.

Key source code

The core of this example is softphone_IncomingCall(). Every time a new call arrives, it creates a fresh CallHandler for that call alone, subscribes to its Completed event so it can be removed once finished, and adds it to a shared, lock-protected list before starting it. This is what gives each simultaneous call its own independent object instead of reusing shared state.

softphone_IncomingCall method creating an independent CallHandler per call
Key source code

Capture SIP traffic with Wireshark

Start a Wireshark capture on the network interface used by the softphone, then place two overlapping calls as in the test above. Apply a SIP display filter to isolate the signaling, and compare the Call-ID header on each INVITE to confirm the two calls are tracked as fully separate SIP dialogs.

What is a SIP INVITE PDU for the first call

This is the INVITE PDU Asterisk generates for the first incoming call, identified by its own unique Call-ID header. Every SIP dialog is tracked by this Call-ID, so as long as it stays unique per call, the PBX and the softphone can keep each call's signaling completely separate even while both are active.

SIP INVITE PDU for the first call showing its unique Call-ID
Figure 1 - SIP INVITE PDU for the first call showing its unique Call-ID

What is a SIP INVITE PDU for the second call

This is the INVITE PDU for a second call placed while the first one is still active, captured moments later in the same session. Its Call-ID is completely different from the first call's, and its SDP body advertises its own RTP port, confirming the two calls get independent media streams even though they are happening at the same time.

SIP INVITE PDU for the second call showing a different Call-ID
Figure 2 - SIP INVITE PDU for the second call showing a different Call-ID

Debug SIP events in Asterisk

Connect to the Asterisk console and enable SIP debug output, then place the same two overlapping calls used in the Wireshark capture. Check that Asterisk creates a separate channel for each call, list both with core show channels while they're active, and confirm one disappears when you hang it up while the other keeps running.

This example uses three classes

  • Softphone.cs — declares and initializes the softphone.
  • CallHandler.cs — manages a single incoming call.
  • Program.cs — runs the console app and creates a CallHandler per call.

Softphone.cs

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.

CallHandler.cs

Each inbound call gets its own CallHandler instance, which plays a sample mp3 to the caller through the speaker. If several calls come in at once, each caller hears the same mp3 file independently, at whatever point it's reached since that call started — because each call's playback state lives in its own handler.

Objects

Each handler owns an ICall, a MediaConnector, an MP3StreamPlayback, and a PhoneCallAudioSender. Learn more about these media handler types here.

ICall call;
MediaConnector mediaConnector;
MP3StreamPlayback mp3Player;
PhoneCallAudioSender phoneCallAudioSender;

public event EventHandler Completed;

The constructor of the class gets an ICall type parameter and it makes the basic setups for the class. Initializes the necessary objects and attaches the phoneCallAudioSender to the call. Besides this creates an MP3StreamPlayback object with file path parameter and connects it to the PhoneCallAudioSender via the mediaConnector.

public CallHandler(ICall call)
{
	this.call = call;
    phoneCallAudioSender = new PhoneCallAudioSender();
    mp3Player = new MP3StreamPlayback(@"..\..\test.mp3");
    mp3Player.Stopped += mp3Player_Stopped;
    phoneCallAudioSender.AttachToCall(call);
    mediaConnector = new MediaConnector();
    mediaConnector.Connect(mp3Player, phoneCallAudioSender);
}

Methods

A method is a code block that contains a series of statements. One of the most important methods of this class is the Start() method because that will be called by the main method of the program. In this method you can subscribe on the CallStateChanged event and accept the call. The call state change event is one of the most important events to notice. This informs both the server and the client about a change in the call state.

public void Start()
{
    call.CallStateChanged += call_CallStateChanged;
    call.Answer();
}

void call_CallStateChanged(object sender, CallStateChangedArgs e)
{
    if (e.State == CallState.Answered)
        mp3Player.Start();
    else if (e.State.IsCallEnded())
        OnCompleted();
}

When the mp3 finishes, mp3Player_Stopped hangs up the call. OnCompleted() then disposes this handler's own media objects only, leaving every other active call untouched.

void mp3Player_Stopped(object sender, EventArgs e)
{
    call.HangUp();
    OnCompleted();
}

void OnCompleted()
{
    mediaConnector.Dispose();
    mp3Player.Dispose();

    var handler = Completed;
    if (handler != null)
        handler(this, EventArgs.Empty);
}

Conclusion

This tutorial covered handling more than one call at the same time by giving each incoming call its own CallHandler instance, then verified that concurrency at the protocol level by inspecting distinct Call-IDs in Wireshark and separate channels in Asterisk's SIP debug log.

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

Related Pages


More information