Python PJSUA Concepts
TracNav
Asynchronous Operations
If you have developed applications with PJSIP, you'll know about this already. In PJSIP, all operations that involve sending and receiving SIP messages are asynchronous, meaning that the function that invokes the operation will complete immediately, and you will be given the completion status as callbacks.
Take a look for example the make_call() method of the Account class. This function is used to initiate outgoing call to a destination. When this function returns successfully, it does not mean that the call has been established, but rather it means that the call has been initiated successfully. You will be given the report of the call progress and/or completion in the on_state() method of CallCallback class.
Relationship Between Objects and Handles
Since the pjsua Python module is based on PJSUA API, the bulk of the processing is done by PJSUA-LIB, including the management of accounts, calls, and buddies. The PJSUA-API exports these objects as handles, while the pjsua Python module just wraps these handles into classes.
By convention, deleting the pjsua Python object will not delete the underlying handle. For example, calling these:
del acc del call del buddy
will not destroy the underlying account, call, and buddy handles in PJSUA, so rather you should do these instead:
acc.delete() del acc call.hangup() del call buddy.delete del buddy
The Main Classes
The PJSUA Python Module Reference Manual shows quite many classes there, and it is probably not very easy to spot which are the important classes. But actually the concept is quite simple. Here are the main classes of the library.
- Lib class:
- This is the main class of the library. You need to instantiate one and exactly one of this class, and from the instance you initialize and start the library, and create other objects such as transports and accounts.
- Transport class:
- A transport more or less specifies a listening socket. You need to create one or more SIP transports before the application can send or receive SIP messages, and the transport creation will return a Transport object. There is not much use of the Transport object, except to add a local account (which is optional if you have a real account) and to display list of listening socket addresses to users if you want to.
- Account class:
- An account specifies the identity of the person (or endpoint) on one side of SIP conversation. At least one Account instance needs to be created before anything else, and from the account instance you can start making/receiving calls as well as adding buddies.
- Call class:
- This class is used to manipulate calls, such as to answer the call, hangup the call, put the call on hold, transfer the call, etc.
- Buddy class:
- This class represents a remote buddy (a person, or a SIP endpoint). You can subscribe to presence status of a buddy to know whether the buddy is online/offline/etc., and you can send and receive instant messages to/from the buddy.
Basic Usage Pattern
With the methods of the main classes above, you will be able to invoke various operations to the object quite easily. But how can we get events/notifications from these classes?
It's with the callback classes.
Each of the main classes above (except Lib) will report their events to the callback instance that is installed to them. So to get and handle these events, just derive a class from the corresponding callback class (AccountCallback, CallCallback, or BuddyCallback), implement the relevant method (depending on which event you want to handle), and install the callback instance to the object.
More will be explained in later chapters.
Error Handling
By convention, we use exceptions as means to report error, as this would make the program flows more naturally. Operations which yield error will raise pjsua.Error exception. Here is a sample:
import pjsua try: call = acc.make_call('sip:buddy@example.org') except pjsua.Error, err: print 'Exception has occured:', err except: print 'Ouch..'
The sample above will print the full error information to stdout. If you prefer to display the error in more structured manner, the pjsua.Error class has several members to explain the error, such as the object name that raised the error, the operation name, and the error message itself.
Threading and Concurrency
For platforms that require polling, the pjsua module provides it's own worker thread to poll pjsip, so it is not necessary to instantiate own your polling thread. Having said that the application should be prepared to have the callbacks called by different thread than the main thread.
The pjsua module should be thread safe.
Internally, the pjsua module actually is not re-entrant, since we found there is a deadlock somewhere when more than one threads are used. The library uses one single lock object to prevent the main thread and pjsua module worker thread from deadlocking.
The lib.auto_lock() returns an object that automatically acquires the library lock and it will automatically release the lock when the object is destroyed. So to protect a function, the code will be like this:
def my_function(): # this will acquire library lock: lck = pjsua.Lib.instance().auto_lock() # and when lck is destroyed, the lock will be released