The call sample application is a voice chat application based on either GIPS VoiceEngine Lite or Linphone, depending on your operating system. It is a command-line application that provides basic alerts and presence notification. libjingle uses a variant of the Jingle extension for audio sessions (XEP-0167) to negotiate the details of the audio connection. These documents do not describe the actual protocol that manages an audio session. Instead, you should rely on the code to manage the protocol for you.
The following diagram shows the relationships between the main classes in the call application.
The previous diagram shows the main objects in the call code. The diagram shows the inheritance path of each object, shown in lighter text on the top right of the object, and important member variables, listed on the bottom of the object.
The user interface has two classes that are not discussed in detail here. The Console class manages asynchronous input and output to the monitor, and the CallClient class is the top level managing class that handles making and answering a call.
Some objects in this diagram are discussed in the overview of a general libjingle application. The following objects are specific to this application:
CallClient is the top level manager for all calls in a voice chat application.
PhoneSessionClient is the top level manager for Call objects, each of which represents one or more voice chat connections. It also is the top level manager for handles creating the offer description by collecting the list of codecs supported by the computer. In addition to the base SessionClient tasks, it handles voice chat specific tasks such as reading and generating codec lists, choosing a codec, and managing global audio settings. It creates Call objects and the ChannelManager object. The application instantiates this object.
Call handles one or more Session objects, each representing a connection between two users. A multi-person chat would consist of a single Call object managing several Session objects, one per connection. The Call object handles the top-level jobs for that call, for example ending sessions, making a call to another user, accepting an incoming call, handling audio tasks such as mute and monitoring, and so on. It sends signals when sessions are created, ended, or change state (connecting, running, and so on). For outgoing calls, the application instantiates this object; for incoming calls, it is instantiated for you by PhoneSessionClient.
ChannelManager is responsible for setting up and destroying the voice channels used to conduct voice chats, as well as specifying some audio settings on those channels. It creates a helper object, MediaEngine (not shown), that is specific to the type of media engine used (Linphone or GIPS VoiceEngine Lite), and also creates the individual VoiceChannel objects, one for each Session object. ChannelManager is instantiated by PhoneSessionClient when it is instantiated.
VoiceChannel connects the capturing/rendering object (MediaEngine) and the socket that sends and receives audio data from the network via a TransportChannel. Each voice channel is associated with one Session, one TransportChannel, and one MediaChannel. It monitors and sends notifications about the quality of the media channel with the help of the AudioMonitor helper class (not shown). VoiceChannel is instantiated by the ChannelManager at the request of the Call object.
MediaChannel is a base class that is extended to control specialized third-party audio packages (such as GIPS VoiceEngine Lite or Linphone). It is used by the specialized MediaEngine instance for that audio package to handle lower level tasks such as sending and receiving audio packets. Each voice channel requires a MediaChannel handle. When ChannelManager creates a new voice channel it also instantiates the proper derived version of this class.
LinphoneMediaEngine / GipsLiteMediaEngine are specialized extensions of MediaEngine. They control the higher-level aspects of audio, such as setting the hardware and audio options. It creates and returns a specialized MediaChannel implementation to a caller, which uses that to control the settings in the MediaEngine. ChannelManager instantiates this class when it is created. There can be only one media engine per PhoneSessionClient.
Linphone / GIPS VoiceEngine Light are third-party audio packages that work with the hardware on your computer to render or capture audio. GIPS VoiceEngine Lite is a limited version of GIPS for Windows computers; Linphone is used on Linux/UNIX computers. The appropriate MediaEngine-derived class instantiates these objects.
Tasks, Session, SessionManager, P2PTransport, ProxyConnection/TCPConnection, XmppPump, and the Port objects are all covered in the discussion about generic libjingle applications.
The call sample application follows the general outline given in Generic libjingle Applications but has a few details worth noting:
The voice chat creates RTP channels, which enables faster data streaming at the expense of potentially lost packets. The channel type is specified in the Session::CreateChannel method by specifying "rtp" as the channel name.
The voice chat example attempts to start sending and receiving data without waiting for the call recipient to accept.
The following sections cover the basic steps handled by the voice chat example:
In the call sample program, the custom CallClient object handles most of these steps. To see how to set up and use CallClient, see the main function in call_main.cc, or examine callclient.cc to learn how CallClient handles the main tasks.
The high-level object that manages the important actions in a voice call is called (appropriately) Call. A Call object manages any number of peer-to-peer Session objects, each representing one peer-to-peer connection. The Call object is the top level object to make calls, accept or reject incoming calls, monitor the status of the call, and performs other high level actions on call connections. CallClient wraps all required steps for making a call in its MakeCallTo method. Here are the basic steps taken by CallClient:
The following code from CallClient::MakeCallTo starts a call to another user, specified by JID.
// Let us know when the Call object is destroyed, so we can close the UI
// or alert the user.
// This only needs doing once per PhoneSessionClient.
phone_client()->SignalCallDestroy.connect(this, &CallClient::OnCallDestroy);
...
// Create the call object.
call_ = phone_client()->CreateCall();
// Connect to receive session notifications.
call_->SignalSessionState.connect(this, &CallClient::OnSessionState);
// Make the connection request to the other user
session_ = call_->InitiateSession(buddy_jid);
// libjingle audio engine only handles one active voice channel at a time.
// Set the focus on the newly created conversation.
phone_client()->SetFocus(call_);
...
// Listen to the progress of the call and alert the user.
void CallClient::OnSessionState(cricket::Call* call,
cricket::Session* session,
cricket::Session::State state) {
if (state == cricket::Session::STATE_INPROGRESS) {
console_->Print("Call connected.");
} else if (state == cricket::Session::STATE_RECEIVEDREJECT) {
console_->Print("Other side rejected the request.");
}
...other conditions...
}
The following example code demonstrates receiving a call. This code is taken from callclient.cc.
// Handler connected to PhoneSessionClient::SignalCallCreate on initialization.
void CallClient::OnCallCreate(cricket::Call* call) {
call->SignalSessionState.connect(this, &CallClient::OnSessionState);
}
// Handler called when a Session object changes state or is first created.
void CallClient::OnSessionState(cricket::Call* call,
cricket::Session* session,
cricket::Session::State state) {
if (state == cricket::Session::STATE_RECEIVEDINITIATE){
// This is an incoming call. Alert the user of the caller's JID.
buzz::Jid jid(session->remote_address());
console_->printf("Incoming call from '%s'", jid.Str().c_str());
// In this thread or in a new thread, request user input to accept or reject the call.
// Input not shown.
... bool acceptCall = user input value;
// Accept or reject the call.
// The call will be the first item in the Call::Sessions() collection.
if(acceptCall){
call_->AcceptSession(call_->sessions()[0]);
phone_client()->SetFocus(call_);
}
else{
call_->RejectSession(call_->sessions()[0]);
}
}
... other session states ...
}
There are no additional steps to take for cleaning up beyond the standard cleanup tasks, (The Call object handles deletion of the VoiceChannel objects by calling ChannelManager::DestroyVoiceChannel.)
The call sample application exposes one additional thread: this is the input/output thread used by the Console class to write and read user input. The other two threads are described in the threading topic. ChannelManager and the MediaEngine/MediaChannel classes also live in the worker thread. In the call example, CallClient::InitPhone() creates a worker thread for you.
The following table lists the most important signals sent by the voice chat application. How to listen for signals is described in Signals.
| Signal | Description |
|---|---|
| SessionManager::SignalRequestSignaling | Sent by the SessionManager to indicate that it is ready to start sending messages over the messaging thread. Signals include candidate generation and socket availability. When this signal is sent, simply respond by calling SessionManager::OnSignalingReady. |
| PhoneSessionClient::SignalCallCreate | Sent whenever a Call object is created. An incoming call request will trigger libjingle to create a new Call object that hosts a new Session object, and you must be prepared to catch that event. The call sample application also creates Call objects for outgoing calls in a different class in response to an XMPP request, so catching this signal will also alert you as to when your outgoing call is connecting. In the listening function, connect to the new Call object's SignalSessionState. |
| PresencePushTask::SignalStatusUpdate | Sent by the presence task whenever someone on your roster joins or leaves the XMPP server. You can catch this message to add or remove entries in the user interface. See Sending and Querying Presence for more information on using this object. |
| Call::SignalSessionState | Sent when a session's state changes. You will need to receive these notifications to recognize whether a new session is an incoming or outgoing call request, whether an outgoing request has been accepted or rejected, if the connection has been made, and other notifications. |