5 H.323 Endpoint
OpenH323 implements the behaviour of a H.323 endpoint in a class named simply H323EndPoint. The class has a number of virtual methods that allow the programmer to customize the behaviour of the endpoint. The virtual methods also serve as a communication interface through which OpenH323 notifies the application about important events — the arrival of a new call, the end of a call, etc.
The task for the application programmer is to derive a new class off of H323EndPoint and override a number of H323EndPoint's methods. A note for those who are new to polymorphism: If you wish to override the behaviour of a method, the prototype of the method in the descendant class must be exactly the same as in the parent class. A failure to do so means that you overload the method which is different than overriding.
An important thing to remember about the use of virtual methods in OpenH323 is that the code inside the methods should not take long to execute. You have to be aware about timeouts in the negotiation of the H.323 connection. The other party might give up the call if your endpoint fails to respond in time because it got delayed in a virtual method. The virtual method should usually only pass information about the event to your application code and return. A good example is that you should never wait for user's answer inside OnAnswerCall().
Our tutorial application declares the descendant of class H323Endpoint in file ep.h. The new class is called MyEndPoint (file ep.h, line 31). MyEndPoint needs to have access to the application's configuration — to know the listen port number or the user's alias, for example. Because of that, MyEndPoint has a reference to class ProgConf (file ep.h, line 34).
The task of MyEndPoint's method Init() (line 38) is to initialize the endpoint. The initialization means, among other things, that the endpoint starts to listen for incoming connections and (optionally) registers with the gatekeeper.
Lines 39–52 list the methods that MyEndPoint needs to override. The names of the methods are more or less self-explanatory. OnConnectionEstablished() is called whenever a new connection has been successfully established. OnConnectionCleared() is called when a connection has been closed. OpenAudioChannel() is called when the OpenH323 stack needs to (create and) open a new audio channel. The method OnAnswerCall() is called when the stack needs to decide whether it should accept a call (this is a somewhat simplified explanation, we will get back to this bellow). At last, OnStartLogicalChannel() is called each time a logical channel has been started. Note that there are many other virtual methods defined in H323EndPoint but our application does not need to override them all.
Let us now describe the methods in file ep.cxx. We will first focus on MyEndPoint's method Init() (lines 76–140). The task of the method is to initialize the H.323 endpoint, i.e. to set user alias, set codecs, start listening for incoming connections, and optionally register with a gatekeeper. We describe the individual steps in more detail bellow.
The code in lines 79–84 first checks whether the string array progConfig.userAliases is non-empty and if so, it inserts the endpoint's aliases. If we set no aliases, the default alias is the user's login name.
When using SetLocalUserName(str), you should be aware that it first empties the endpoint's list of aliases and then sets str as the first (and only) alias. If you need to give the endpoint several aliases, use SetLocalUserName() for the first one and AddAliasName() for the second alias, the third, etc.
The endpoint class stores the aliases as a list of strings. When it builds H.225 messages, the alias strings are inserted as the AliasAddress type — either as the choice dialedDigits (if the string only contains characters from "0123456789*#") or as h323-ID.
Lines 90–101 tell MyEndPoint which codecs it should use. The order in which we add the codecs sets their priority. Our application will have the Speex codec at 8000 bps as the most preferred codec, GSM 06.10 as the second most preferred, G.711 uLaw will be the third, and G.711 ALaw the fourth. Note that Speex and GSM will only be added if the application can find the codec plugins (see the note at the end of Section 2). Read the document at VoxGratia for details on plugin audio codecs.
We show three methods that you can use to add a capability to the capability list of the endpoint:
- The method AddAllCapabilities() lets you add all codecs whose names match the given string.
- H323Capability::Create() creates a capability object. The given string has to match a codec name exactly, otherwise the method returns NULL.
- You can create the capability objects with new and insert them to the capability list using the method SetCapability().
We also set the number of codec frames in outgoing RTP packets for the GSM codec. The codec uses frames of 20 milliseconds each, so one transmitted RTP packet will carry 80 milliseconds of audio (four frames). The OpenH323 site has a table with data about frame size, frame duration, and bandwidth for a number of codecs.
Line 103 inserts into the endpoint's capability table all the available DTMF capabilities. If the application runs with tracing level 1 or higher, line 105 sends the endpoint's capability table to the trace output.
Lines 108–117 allocate and initialize an object listener of class H323ListenerTCP. The task of the object is to listen for incoming H.323 connections. The constructor takes three parameters — the first is a reference to H323EndPoint (or its descendant), the second parameter is the address of the network interface at which we wish to listen. The third parameter is the listen port number (we use the value stored in progConf.port). The value INADDR_ANY we have in the variable addr means that we want to listen at all available interfaces. Note that the application programmer is responsible for the deallocation of the object listener if the object fails to start properly (i.e. StartListener() returns FALSE).
Lines 120–137 deal
with endpoint's registration with a gatekeeper.
If we do not wish to register, we simply flag the registration as successful
and go on (the case in line 123).
If we need to register, we call H323EndPoint's method
UseGatekeeper(). This method calls one of four other
methods, namely DiscoverGatekeeper(), LocateGatekeeper(),
SetGatekeeper(), and SetGatekeeperZone(), using the
criteria shown in the table below:
|GK address available||Gatekeeper Id available (a.k.a. Zone Name)||Method called|
The first packet that is sent out by the endpoint during the registration process carries the H.225 RAS Gatekeeper Request message (GRQ). If the gatekeeper's address is not known (i.e. with DiscoverGatekeeper() and LocateGatekeeper(), respectively), the GRQ message is sent using multicast. The multicast address assigned to gatekeeper registration is 18.104.22.168. If the zone name is available (LocateGatekeeper() and SetGatekeeperZone(), respectively), the endpoint puts it into GRQ's optional field gatekeeperIdentifier. If the registration fails, line 135 outputs a simple error message and Init() returns with false. Otherwise, the endpoint has been initialized successfully and Init() returns true.
5.2.2 Virtual methods
We now focus on implementation of MyEndPoint's virtual methods.
The virtual method MyEndPoint::OnConnectionEstablished() (file ep.cxx, lines 45–49) is called when the H.323 connection has been successfully established. Our tutorial application only uses this method for a trace output, but you can probably imagine that OnConnectionEstablished() is very important for real-world applications. As an example, if you need to do some kind of billing, this callback denotes the beginning of the billed call duration.
OnConnectionEstablished() has two parameters. The first is a reference to an H323Connection object (the object is locked, you have an exclusive access to it). The second parameter (const PString & token) is a reference to a string that uniquely identifies a particular connection within the OpenH323 endpoint. Do not mistake OpenH323 call tokens for H.323 call identifiers. The call tokens are internal to the OpenH323 library and they do not appear in any message sent by the endpoint. Call tokens are constructed using host names and port numbers and are thus human readable — this can help you when debugging your program. Whenever your application needs to associate its data with individual H.323 calls, call tokens are the natural link.
The virtual method MyEndPoint::OnConnectionCleared() (file ep.cxx, lines 54–58) is called when a connection has been closed. Its parameters are the same as those of OnConnectionEstablished(). If you need to know the reason why the call ended, you can obtain it by calling connection.GetCallEndReason(). The data type returned by this method is enum CallEndReason — please refer to openh323/include/h323con.h for all the possible values.
The third virtual method we deal with is MyEndPoint::OnAnswerCall() (file ep.cxx, lines 63–71). This callback is the place where the application programmer decides whether the OpenH323 stack should accept or reject an incoming call. The method has four parameters. The first parameter is a reference to an H323Connection object, while the second is a PString giving the name (textual description) of the caller. The third parameter is a constant reference to the Q.931/H.225 Setup PDU the local endpoint received from the caller. The fourth parameter is a reference to the Connect PDU that would indeed be sent if the call is accepted.
OnAnswerCall() is not the only callback that is used when deciding about the acceptance or rejection of an incoming call. The class H323EndPoint has another virtual method, OnIncomingCall() (see openh323/include/h323ep.h for its exact prototype). When a remote endpoint wants to establish a H.323 connection, it first sends Setup PDU. The local OpenH323 stack responds with Call Proceeding PDU and then invokes the OnIncomingCall() callback. If OnIncomingCall() returns TRUE, the local endpoint sends the Alerting PDU and then calls OnAnswerCall(). So if you know already at the moment when OnIncomingCall() is called that you cannot accept the call (e.g. your application is low on particular resources), you can reject the call by returning FALSE. Otherwise, you do not need to override OnIncomingCall() — its default behaviour is to return TRUE — and leave the handling up to OnAnswerCall().
Our tutorial application always accepts incoming calls, so MyEndPoint::OnAnswerCall() simply returns H323Connection::AnswerCallNow. If we wanted to refuse the call, the value returned would be H323Connection::AnswerCallDenied. As mentioned at the top of this chapter, if you cannot immediately decide whether to accept a call (which would be the case with most real-world programs), it would not be correct to wait inside OnAnswerCall(). Instead, OnAnswerCall() should just notify the application about the new incoming call and then return H323Connection::AnswerCallPending. The OpenH323 stack will then send the Alerting PDU and the application can accept or reject the call later using the method AnsweringCall() of the class H323Connection.
MyEndPoint::OpenAudioChannel (file ep.cxx, lines 145–163) is another virtual method that is important for the functionality of our tutorial application. This callback allows the application to set the source and/or destination of audio to something else than the default sound card. Three out of the four parameters of OpenAudioChannel() are important for our application: H323Connection & connection is a reference to the connection for which we create the audio channel. BOOL isEncoding tells us what is the direction of the audio data. If isEncoding is true, the audio channel will act as a source of audio data to be encoded and transmitted to the remote endpoint, otherwise the audio channel will be used as a destination for the incoming audio. The third important parameter is H323AudioCodec & codec which gives us access to the codec object that will either encode the outgoing audio or decode the incoming audio.
Let us now describe the code inside OpenAudioChannel(). The commented line 150 is an example of how to disable silence detection. Line 151 tests the value of isEncoding. If it is true (i.e. we deal with outgoing audio), we create an object of class WavChannel. The constructor of the WavChannel class needs two parameters — the first one is a name of a WAV file while the second parameter is a reference to the H323Connection object that will use the channel object (see Section 6 for a detailed discussion of the audio channels). Having created the channel object, we attach it to the codec (line 154). The value true passed as the second parameter of AttachChannel() means that the codec should deallocate the channel object when it is no longer needed. AttachChannel() returns a boolean value indicating whether the operation succeeded and we simply use this boolean as the return value of OpenAudioChannel(). The code for the case when isEncoding is false (incoming audio) is almost identical. The only difference is that we crate an instance of class NullChannel instead of WavChannel.
The endpoint's virtual method OnStartLogicalChannel() is called when the OpenH323 stack has successfully started a thread responsible for either transmitting or receiving of audio or video data. Our tutorial application only uses audio, so we will have two logical channel threads for each call. We use the the virtual method MyEndPoint::OnStartLogicalChannel() (file ep.cxx, lines 168–187) to output trace information about the codec (capability) used for the incoming or outgoing data. The method is called once for each channel, so if the trace output for a particular call contains only one or no line with "Started logical channel...", we know we should start looking for H.323 negotiation problems, e.g. mismatched codecs or insufficient allocated bandwidth.
Next: 6 Audio Channels