Home · All Classes · Grouped Classes · Annotated · Functions

VoIP Integration

VoIP Integration

This document describes how to integrate a third-party Voice over IP (VoIP) agent into Qtopia. However large parts of this guide refer to general telephony interfaces and classes. This means that this document also describes the essential steps to integrate an arbitrary telephony service.

Third parties need to supply at least three components as follows:

  1. A VoIP agent daemon that provides the call management facilities to the Qtopia telephony server. This is a Qtopia application that links against the Qtopia telephony libraries to communicate with the Qtopia telephony server. It typically does not have any user interface and runs in the background as a daemon.
  2. A settings program that allows the user to configure the VoIP options for the VoIP agent daemon.
  3. A call policy manager task in the qpe server to provide user interface integration with Qtopia's call screen.

To demonstrate the process, this document describes how to integrate the IAX2 protocol used by the Asterisk telephony server. We will use the open source iaxclient library to provide the IAX2 protocol implementation. The full source code for the Asterisk integration can be found in the Qtopia source tree under examples/asterisk.

The Qtopia source tree also contains a SIP-based VoIP agent that uses the open source libdissipate2 library from the KPhone project. We will briefly touch on this SIP integration below when we discuss presence, because Asterisk does not support presence. The full source code for the SIP integration can be found in the Qtopia source tree under src/tools/sipagent and src/settings/sipsettings.

Note: Currently due to licensing inconsistencies libdissipate2 cannot be shipped with Qtopia.

In the examples below, the virtual keyword will be shown on methods that must be overridden from a parent class. If a method does not have the virtual keyboard, then it is unique to the subclass.

Qtopia Phone Library

The integration model for VoIP agents uses the Qtopia Phone Library to provide the basic infrastructure. The phone library divides up all telephony functionality into services and interfaces.

Handlers in the telephony system are referred to as services. Each service has a unique name, such as modem, voip, etc. By convention, service names are lower case. Within each service is a list of interfaces for functionality areas such as network registration, phone calls, presence, SMS, etc.

Interface names correspond to class names in the Qtopia Telephony API. For example, the QNetworkRegistration interface provides access to network registration features, and the QPresence interface provides methods to access user presence information.

A more thorough overview of the library can be found on the Qtopia Phone Library page in the documentation.

Service names should be unique. The name modem is reserved for use by GSM modems, and the name voip is reserved for SIP-based VoIP services. The sipagent program in Qtopia normally implements the voip service. If you wish to replace it with another SIP implementation, then use the name voip in your replacement. But if you wish to keep using sipagent, then you should choose a new name for your own service. The example in this document uses the name asterisk.

Basic Asterisk Telephony Service

To integrate Asterisk, we need to provide a telephony service called asterisk which inherits from the QTelephonyService class. The most basic definition is as follows (from iaxtelephonyservice.h):

    class IaxTelephonyService : public QTelephonyService
    {
        Q_OBJECT
    public:
        IaxTelephonyService( const QString& service, QObject *parent = 0 );
        ~IaxTelephonyService();

        virtual void initialize();

        ...
    };

The constructor and destructor initialize and shut down the iaxclient library (for the full details, see the source code in the examples/asterisk/iaxagent directory):

    IaxTelephonyService::IaxTelephonyService
            ( const QString& service, QObject *parent )
        : QTelephonyService( service, parent )
    {
        // Initialize the iaxclient library
        ...
    }

    IaxTelephonyService::~IaxTelephonyService()
    {
        // Shut down the iaxclient library
        ...
    }

All telephony services must override the initialize() method to create the telephony interfaces that it requires. For Asterisk, we need the QNetworkRegistration, QTelephonyConfiguration, QServiceChecker, and QPhoneCallProvider interfaces:

    void IaxTelephonyService::initialize()
    {
        if ( !supports<QNetworkRegistration>() )
            addInterface( new IaxNetworkRegistration( this ) );

        if ( !supports<QTelephonyConfiguration>() )
            addInterface( new IaxConfiguration( this ) );

        if ( !supports<QServiceChecker>() )
            addInterface( new IaxServiceChecker( this ) );

        if ( !callProvider() )
            setCallProvider( new IaxCallProvider( this ) );

        QTelephonyService::initialize();
    }

The telephony service must call the base QTelephonyService::initialize() function to complete the initialization process.

Starting up the Asterisk Agent

When VoIP telephony services are started, they receive a QCop message Telephony::start() on their application channel. When they are shut down, they receive a QCop message Telephony::stop() on their application channel. For more information on QCop service messages, see the Qtopia Services documentation.

We can intercept these messages using the QtopiaAbstractService class:

    class IaxTelephonyServiceQCop : public QtopiaAbstractService
    {
        Q_OBJECT
    public:
        IaxTelephonyServiceQCop( QObject *parent = 0 );
        ~IaxTelephonyServiceQCop() {}

    public slots:
        void start();
        void stop();

    private:
        IaxTelephonyService *service;
    };

    IaxTelephonyServiceQCop::IaxTelephonyServiceQCop( QObject *parent )
        : QtopiaAbstractService( "Telephony", parent )
    {
        publishAll();
        service = 0;
    }

    void IaxTelephonyServiceQCop::start()
    {
        if ( !service ) {
            // Register a task to keep us alive while the service is running.
            QtopiaApplication::instance()->registerRunningTask
                ( "IaxTelephonyService", this );

            // Create the service handler, registered under the name "asterisk".
            qLog(VoIP) << "Starting iaxclient service handler";
            service = new IaxTelephonyService( "asterisk", this );
            service->initialize();
        }
    }

    void IaxTelephonyServiceQCop::stop()
    {
        if ( service ) {
            // Delete the service handler.
            qLog(VoIP) << "Stopping iaxclient service handler";
            delete service;
            service = 0;

            // Deregister the task which allows this daemon to shut down.
            QtopiaApplication::instance()->unregisterRunningTask
                ( "IaxTelephonyService" );
        }
    }

It is important that the agent register a running task using QtopiaApplication::registerRunningTask() so that Qtopia will not shut down the agent prematurely. It is also important to create the telephony service properly, and to not forget the call to initialize():

    service = new IaxTelephonyService( "asterisk", this );
    service->initialize();

If you forget the call to initialize(), the service will not be registered with the system and phone calls will not work.

The final coding step is to start the QCop service from the agent's main function. See main.cpp in the examples/asterisk/iaxagent directory for the details.

We now need to register the iaxagent daemon as a Qtopia telephony service by adding an iaxagent to the directory $QPEDIR/services/Telephony whose contents is as follows:

    [Extensions]
    [Standard]
    Version = 100

Now when Qtopia starts up, it will detect the new telephony service and send it the Telephony::start() QCop message.

Service Checking

When a telephony service starts up, it may not be ready to register to the network immediately. Some time may be required to properly initialize the service, or the service may not be properly configured.

The Qtopia server uses the QServiceChecker interface to determine if the service is ready to be used. If the telephony service does not provide an implementation of QServiceChecker, then Qtopia will assume that the service is ready to use as soon as initialize() returns.

For our Asterisk service, we need to wait for the registration server to be configured in the Asterisk.conf file before it can be used. So we have to implement a new service checker as follows (the actual code is in iaxservicechecker.h and iaxservicechecker.cpp):

    class IaxServiceChecker : public QServiceChecker
    {
        Q_OBJECT
    public:
        explicit IaxServiceChecker( IaxTelephonyService *service );
        ~IaxServiceChecker() {}

    public slots:
        void updateRegistrationConfig();
    };

    IaxServiceChecker::IaxServiceChecker( IaxTelephonyService *service )
        : QServiceChecker( service->service(), service, Server )
    {
        updateRegistrationConfig();
    }

    void IaxServiceChecker::updateRegistrationConfig()
    {
        QSettings config( "Trolltech", "Asterisk" );
        config.beginGroup( "Registration" );
        if ( config.value( "Server" ).toString().isEmpty() )
            setValid( false );
        else
            setValid( true );
    }

Now, we can call updateRegistrationConfig() whenever the configuration file is updated and it will notify Qtopia of whether the Asterisk service can be used or not.

Network Registration

Once a telephony service has been successfully initialized and checked, the next step is to register to the network. Qtopia uses the QNetworkRegistration interface to control the network registration state of telephony services.

There are two general modes that can be used by a telephony service for network registration: automatic and manual. In automatic mode, the telephony service will attempt to register to the network as soon as possible after service initialization. In manual mode, the user must explicitly launch the appropriate settings program and request network registration.

For Asterisk, we override the QNetworkRegistration interface to create the IaxNetworkRegistration class (the actual code is in iaxnetworkregistration.h and iaxnetworkregistration.cpp):

    class IaxNetworkRegistration : public QNetworkRegistrationServer
    {
        Q_OBJECT
    public:
        explicit IaxNetworkRegistration( IaxTelephonyService *service );
        ~IaxNetworkRegistration();

    public slots:
        virtual void setCurrentOperator
            ( QTelephony::OperatorMode mode, const QString& id,
              const QString& technology );
        virtual void requestAvailableOperators();
        void registerToServer();
        void deregisterFromServer();

        ...

    private:
        bool pendingSetCurrentOperator;
    };

    IaxNetworkRegistration::IaxNetworkRegistration( IaxTelephonyService *service )
        : QNetworkRegistrationServer( service->service(), service )
    {
        pendingSetCurrentOperator = false;
    }

    IaxNetworkRegistration::~IaxNetworkRegistration()
    {
        deregisterFromServer();
    }

    void IaxNetworkRegistration::setCurrentOperator
            ( QTelephony::OperatorMode mode, const QString&id,
              const QString& technology)
    {
        if ( mode == QTelephony::OperatorModeDeregister ) {
            pendingSetCurrentOperator = true;
            deregisterFromServer();
        } else {
            pendingSetCurrentOperator = true;
            registerToServer();
        }
    }

    void IaxNetworkRegistration::requestAvailableOperators()
    {
        QList<QNetworkRegistration::AvailableOperator> list;
        emit availableOperators( list );
    }

    void IaxNetworkRegistration::registerToServer()
    {
        ...
    }

    void IaxNetworkRegistration::deregisterFromServer()
    {
        ...
    }

The actual code that performs the registration and deregistration is not shown here. See the source code for the full details. The important point to note is that once the registration has completed, or been lost, the Asterisk service should call QNetworkRegistrationServer::updateRegistrationState() to update the registration state to one of the following values:

QTelephony::RegistrationNoneRegistration is not currently available.
QTelephony::RegistrationHomeThe user is registered to their home network.
QTelephony::RegistrationSearchingThe service is attempting to register but has not done so yet.
QTelephony::RegistrationDeniedThe service tried to register but was denied, probably because the user's authentication credentials were invalid.
QTelephony::RegistrationUnknownThe registration state is unknown. This normally only makes sense for GSM networks. For VoIP implementations, use QTelephony::RegistrationNone instead.
QTelephony::RegistrationRoamingThe user is registered to a network and can place calls, but it is not their usual home network.

If the Asterisk service is set to the automatic registration mode, then these registration state changes will happen shortly after service initialization. If the service is set to the manual registration mode, then these registration state changes will only happen after the user launches the settings program and manually requests registration.

The other main component of network registration is the list of available network operators. This is used by settings programs to allow the user to choose an alternative network in their current location.

For Asterisk, there is only a single operator: the one configured by the user in the Asterisk.conf file. It therefore doesn't make sense to allow the user to choose another operator and we return an empty list from requestAvailableOperators().

For other VoIP implementations, especially those that can roam between public WiFi hotspots, it may make sense to return a non-empty list from requestAvailableOperators(), giving the user to ability to choose from a list of the networks that are in range.

Phone Calls

Once the service has been checked, and the network registered, the next step is to enable the placement of phone calls. Qtopia achieves this in telephony services with the QPhoneCallProvider interface. Our Asterisk agent needs to inherit from QPhoneCallProvider and override the create() method (iaxcallprovider.h and iaxcallprovider.cpp):

    class IaxCallProvider : public QPhoneCallProvider
    {
        Q_OBJECT
    public:
        IaxCallProvider( IaxTelephonyService *service );
        ~IaxCallProvider() {}

        void stateEvent( struct iaxc_ev_call_state *e );

    protected:
        virtual QPhoneCallImpl *create
            ( const QString& identifier, const QString& callType );
    };

    IaxCallProvider::IaxCallProvider( IaxTelephonyService *service )
        : QPhoneCallProvider( service->service(), service )
    {
        setCallTypes( QStringList( "Asterisk" ) );
        ...
    }

    QPhoneCallImpl *IaxCallProvider::create
            ( const QString& identifier, const QString& callType )
    {
        return new IaxPhoneCall( this, identifier, callType, -1 );
    }

The call to QPhoneCallProvider::setCallTypes() in the constructor is very important. It publishes the call types that are understood by the telephony service into the value space. Qtopia uses this information to select an appropriate service to place an outgoing call. If the setCallTypes() function is not called in the constructor, then it will not be possible to place phone calls using the service.

Call types are typically simple names such as Voice, VoIP, Data, Fax, Video, etc. See the documentation for QPhoneCallManager::create() for a description of how call types are used to select an appropriate telephony service.

The VoIP call type is reserved for use by SIP-based telephony services that are intended to replace the reference sipagent implementation. We cannot use that for our IAX2 implementation, so we publish the service's call type as Asterisk instead.

By convention, service names start with a lower-case letter, and call type names start with an upper-case letter. This convention is not strictly enforced by Qtopia, but it can help when diagnosing telephony problems to be able to distinguish a word used as a service name and the same word used as a call type.

This code creates an instance of IaxPhoneCall whenever the user attempts to place an outgoing phone call. It will then be followed by a call to IaxPhoneCall::dial(). The IaxPhoneCall class is declared as follows:

    class IaxPhoneCall : public QPhoneCallImpl
    {
        Q_OBJECT
    public:
        IaxPhoneCall( IaxCallProvider *provider, const QString& identifier,
                      const QString& callType, int callNo );
        virtual ~IaxPhoneCall();

        virtual void dial( const QDialOptions& options );
        virtual void hangup( QPhoneCall::Scope scope );
        virtual void accept();
        virtual void hold();
        virtual void activate( QPhoneCall::Scope scope );
        virtual void tone( const QString& tones );
        virtual void transfer( const QString& number );

        void stateEvent( struct iaxc_ev_call_state *e );

        ...
    };

All telephony services must inherit from QPhoneCallImpl to provide the functions on phone calls. See the documentation for that class for more information on the functionality that is required.

The above applies to outgoing calls. For incoming calls, the Asterisk service detects the call in IaxCallProvider::stateEvent() and then constructs an instance of IaxPhoneCall directly:

    void IaxCallProvider::stateEvent( struct iaxc_ev_call_state *e )
    {
        IaxPhoneCall *call = fromCallNo( e->callNo );
        if ( call ) {
            // State change on a known call.
            call->stateEvent( e );
        } else if ( ( e->state & IAXC_CALL_STATE_RINGING ) != 0 ) {
            // Newly arrived incoming call.
            beginStateTransaction();
            QString identifier = QUuid::createUuid().toString();
            IaxPhoneCall *call = new IaxPhoneCall
                ( this, identifier, "Asterisk", e->callNo );
            call->setNumber( e->remote );
            call->setActions( QPhoneCallImpl::Transfer );
            call->setState( QPhoneCall::Incoming );
            endStateTransaction();
        }
    }

Presence

While the IAX2 protocol does not support presence, most other VoIP protocols do. The sipagent program in the Qtopia sources handles this by inheriting from QPresence (siptelephonyservice.h):

    class SipPresence : public QPresence
    {
        Q_OBJECT
    public:
        SipPresence( SipTelephonyService *service );
        ~SipPresence();

    public slots:
        virtual bool startMonitoring( const QString& uri );
        virtual bool stopMonitoring( const QString& uri );
        virtual void setLocalPresence( QPresence::Status status );

        ...
    };

The VoIP agent must also add two lines to the initialize() function to create the presence interface at startup time (siptelephonyservice.cpp):

    if ( !supports<QPresence>() )
        addInterface( new SipPresence( this ) );

See the documentation for QPresence for more information on the required functionality for the presence interface.

Configuration

Configuration of VoIP services can be very complicated, which is why we recommend that you write a separate settings program that asks the user for the relevant details and then writes them to a configuration file that the agent daemon can access. For the Asterisk example, the source code for its settings program can be found under examples/asterisk/iaxsettings.

Once the configuration file has been updated, it is necessary to notify the agent daemon that it needs to reload the configuration. If your VoIP agent already has a way of performing this notification, then you can stop here. Or you can use the QTelephonyConfiguration interface that Qtopia provides.

Our Asterisk agent daemon understands two configuration messages from the iaxsettings program: registration and callerid. The former changes the registration settings, and the latter changes the user's caller-id (name and number) information. We implement this in the IaxConfiguration class (iaxconfiguration.h and iaxconfiguration.cpp):

    class IaxConfiguration : public QTelephonyConfiguration
    {
        Q_OBJECT
    public:
        IaxConfiguration( IaxTelephonyService *service );
        ~IaxConfiguration() {}

    public slots:
        virtual void update( const QString& name, const QString& value );
        virtual void request( const QString& name );

    private:
        IaxTelephonyService *service;
    };

    IaxConfiguration::IaxConfiguration( IaxTelephonyService *service )
        : QTelephonyConfiguration( service->service(), service, Server )
    {
        this->service = service;
    }

    void IaxConfiguration::update( const QString& name, const QString& )
    {
        // Process messages from the "iaxsettings" program for config updates.
        if ( name == "registration" )
            service->updateRegistrationConfig();
        else if ( name == "callerid" )
            service->updateCallerIdConfig();
    }

    void IaxConfiguration::request( const QString& name )
    {
        // Not supported - just return an empty value.
        emit notification( name, QString() );
    }

There are two other forms of configuration that can be handled in a special manner: network registration changes and presence changes. The settings program can use QNetworkRegistration::setCurrentOperator() to register to and deregister from the network, and it can use QPresence::setLocalPresence() to update the user's presence state.

It is not a requirement that you use these configuration API's. If your VoIP agent has some other method for changing configuration settings, you are free to use that instead.

Call Policy Managers

New VoIP services need some user interface support in the server to complete the integration. They do this by creating a server task that inherits from QAbstractCallPolicyManager.

The call policy manager tells Qtopia when the service is active, when network registration changes occur, what call type to use for outgoing call requests, and the icons to display to represent network registration and call types. For Asterisk, the call policy manager is declared as follows (asteriskmanager.h in the server sources):

    class AsteriskManager : public QAbstractCallPolicyManager
    {
        Q_OBJECT
    public:
        AsteriskManager( QObject *parent=0 );
        ~AsteriskManager();

        virtual QString callType() const;
        virtual QString trCallType() const;
        virtual QString callTypeIcon() const;
        virtual QTelephony::RegistrationState registrationState() const;
        virtual QAbstractCallPolicyManager::CallHandling handling(const QString& number);
        virtual bool isAvailable(const QString& number);
        virtual QString registrationMessage() const;
        virtual QString registrationIcon() const;

        ...
    };

    QTOPIA_TASK_INTERFACE(AsteriskManager);

The most important function is QAbstractCallPolicyManager::handling(). This helps the server choose an appropriate telephony service when placing an outgoing call (incoming calls are handled implicitly by the service that first announced them). The handling() function inspects the phone number to determine if it can handle it at present. For Asterisk, the code is as follows (asteriskmanager.cpp):

    QAbstractCallPolicyManager::CallHandling AsteriskManager::handling
            (const QString& number)
    {
        // Cannot handle URI's that contain '@' or ':'.
        if (number.contains(QChar('@')) || number.contains(QChar(':')))
            return CannotHandle;

        // If no network registration, then cannot handle at this time.
        if (registrationState() != QTelephony::RegistrationHome)
            return CannotHandle;

        // Assume that this is a number that we can dial.
        return CanHandle;
    }

We first filter out URI's for SIP and other call types that do not use phone numbers. Then we check to see if the Asterisk service is currently registered to the user's home network. If it is, then we indicate that Asterisk can handle the call.

The return value is used by Qtopia to select the appropriate telephony service to make the call. If two or more call policy managers return CanHandle, then the user will be presented with a list to choose from. In the case of Asterisk, this may happen if the user is registered to both Asterisk via WiFi and a GSM cellular network at the same time.

The allowable return values and their meanings are as follows:

QAbstractCallPolicyManager::CannotHandleThis telephony service cannot handle the requested number.
QAbstractCallPolicyManager::CanHandleThis telephony service can handle the requested number.
QAbstractCallPolicyManager::MustHandleThis telephony service must handle the requested number. This is typically used for special emergency calls that must be placed over a GSM network even if an Asterisk network is also registered at the same time. This value overrides the CanHandle answers from all other call policy managers.
QAbstractCallPolicyManager::NeverHandleThis telephony service would normally return MustHandle because it is supposed to handle the call, but the call cannot be placed right now. This is typically used for emergency numbers when the phone device is in flight mode. This value causes Qtopia to reject the call immediately without attempting to place it.

Implementation Checklist

The following summarises the steps involved in integrating a third-party VoIP agent into Qtopia:

Troubleshooting

The following are the most common problems that you are likely to encounter while integrating a third-party VoIP agent:

Service not startedYou must add a service definition file to $QPEDIR/services/Telephony to tell the Qtopia server how to launch your telephony service daemon.
Service starts and then immediately exitsYou must use QtopiaApplication::registerRunningTask() to ensure that Qtopia will keep the daemon running after the Telephony::start() QCop message is received. Upon shutdown, you can call QtopiaApplication::unregisterRunningTask() and your daemon will gracefully exit.
Service not registeredThe service will not be properly registered if you do not call the initialize() function on your QTelephonyService instance. You can detect if this is the case by using vsexplorer to inspect the value space underneath /Communications/QNetworkRegistration/asterisk where asterisk is replaced with the name of your service. There should be entries for requestChannel and responseChannel if the service is properly registered.
Call types are not registeredIt will not be possible to place phone calls if the call types have not been properly registered with setCallTypes(). You can detect this by using vsexplorer to inspect the value space underneath /Communications/QPhoneCallProvider/CallTypes. There should be an entry for your service name (asterisk in our example} listing the supported call types.
Incoming calls are not announcedMake sure that you have created the QPhoneCallImpl instance correctly, and have emitted a state change using QPhoneCallProvider::beginStateTransaction() and QPhoneCallProvider::endStateTransaction(). The same applies when calls change state. If the state transaction is not sent, the rest of Qtopia will be unaware that the call has changed state.
Call policy manager not registeredMake sure that QTOPIA_TASK_INTERFACE(AsteriskManager) appears in the .h file and that QTOPIA_TASK(Asterisk, AsteriskManager), QTOPIA_TASK_PROVIDES(Asterisk, AsteriskManager), and QTOPIA_TASK_PROVIDES(Asterisk, QAbstractCallPolicyManager) appear in the .cpp file, where Asterisk is replaced with the name of your telephony service.


Copyright © 2008 Nokia Trademarks
Qtopia 4.3.3