Home --> Documentations --> Porting Guide
PJLIB, PJLIB-UTIL, PJSIP and PJMEDIA libraries (or will be called just PJ libraries) have been designed specificly to be very portable and have very small footprint, to make it ideal to be used on embedded or even deeply embedded system development.
This article describes the challenges faced when porting PJ software to these "non-standard" platforms and presents some strategies to maximize the chance of successful porting efforts.
Table of Contents
This section describes components that need to be considered when porting PJ libraries to new platform, especially the embedded or small-footprint systems.
For this porting purpose, the target CPU determines the characteristic of the basic data types (more specifically the width and endianness) and the availability of floating point co-processors.
This normally is not something that we should worry about since all libraries are written on C language (there is no assembly instructions at all in the libraries).
PJLIB encapsulates all integral data types into the corresponding pj_* data types in <pj/types.h>. In addition, 64bit integral data types are defined in the appropriate compiler specific header file (e.g. <pj/compat/cc_gcc.h> for GCC, <pj/compat/cc_msvc.h>for MSVC).
Currently the data type mapping is defined below.
The above mappings should work on most (if not all) 32-bit or 64-bit CPU and across different types of compilers, however these may not be accurate for 8-bit or 16-bit CPUs (the int/unsigned int data type may be less than 32-bit wide), therefore developer MUST review and possibly fix these mappings before attempting to compile any codes.
The PJLIB macros PJ_IS_LITTLE_ENDIAN and PJ_IS_BIG_ENDIAN MUST be declared correctly according to the endianness of the target CPU. These macros are declared by processor/machine specific header file <pj/compat/m_*.h>.
Floating point availability is controlled by PJ_HAS_FLOATING_POINT macro, which by default is set to 1 in <pj/config.h>. This can be overriden by declaring PJ_HAS_FLOATING_POINT to zero in your <pj/config_site.h>.
PJLIB, PJLIB-UTIL, and PJSIP does not need any floating point support. Even when floating point is disabled, everything should work correctly. However, the PJ_HAS_FLOATING_POINT macro still MUST be set accordingly in order for some fallback code to get activated.
However, the situation is different in PJMEDIA. Some PJMEDIA components (such as tone generator, RTCP calculation, Speex codec, and AEC) do have fallback algorithm implemented in fixed point, which get activated when PJ_HAS_FLOATING_POINT macro is set to zero. However, some other components do not have the alternative fixed point implementation, thus the floating point based code will get used regardless of the floating point setting (examples of such components are the resampling, PLC, and iLBC codec).
PJ currently supports GCC, MS Visual Studio, MS Embedded VC, Intel C compiler (using gcc defines), and MetroWerks C compiler (works in progress in Symbian port). To support new compiler, a new <pj/compat/cc_*.h> needs to be created, and the <pj/config.h> file needs to be modified to include this new compiler specific header file whenever the use of such compiler is detected.
PJ does not use any compiler specific constructs, so supporting a new compiler should be straightforward.
A 64bit integer/unsigned integer data types are used quite heavily throughout the libraries for representing large integers. The availability of such data types are controlled by PJ_HAS_INT64 macro.
On PJLIB and PJSIP libraries, normally the use of 64bit data types ar optional; when PJ_HAS_INT64 macro is not declared, or when its value is zero, the 64bit data type will be replaced by multiple 32bit integers.
However, PJMEDIA uses 64bit data type as the frame timestamp (to avoid overflow), and this currently does not have the replacement 32bit algorithms. Many PJMEDIA components use the 64bit operations against frame timestamp, without providing alternative 32bit operations.
Most modern compilers such as gcc should have support for 64bit integral data types; however please check this when porting PJ software using other compilers.
If the existing PJ Makefile based build system is to be used, then ideally the compiler should support GCC syntax for specifying options (for example "/I" to specify include search path). If this is not possible, when a new "cc-*.mak" file need to be created in $PJ-ROOT/build/ directory, to tell the Makefile build system how to invoke various options to the compiler (note: the use of compiler other than gcc family for the Makefile has not been tested for some time, so few things may be broken).
Alternatively, you may create a new build system altogether. For example, if the compiler comes with its own build system (an IDE, for example), then probably it's better to create new project files for building the libraries instead of using PJ build system. This is the approach that we use when porting PJ to Windows Mobile and Symbian.
When creating a new build system, please prefer to put the build specific files (such as project files) under each build/ directory of each project, or in a sub-directory under build/ directory in each project, if at all possible. Also take care to create another subdirectory under the project directory, to place the object files, so that it is easy to add Subversion rule to ignore these directories containing output files from being included in the source control.
However, creating a new build system comes at a cost, that is it would need to be maintained to keep it in sync with changes in main PJ libraries. So please weight the convenience of having a build system supported by/integrated with the target against the cost of maintaining such build system when deciding whether choose this route.<stdarg.h> header file should be available, since this is standard ANSI C feature. But should this is not available, then probably we can get away with disabling PJLIB logging functionality by defining PJ_LOG_MAX_LEVEL macro to zero in your <pj/config_site.h>, but definitely this practice is highly discouraged since having logging is crucial when debugging the libraries!
The LIBC header availabilities should be indicated by the corresponding PJ_HAS_*_H macro in the appropriate OS specific <pj/compat/os_*.h> header file (for example, PJ_HAS_STRING_H indicates the availability of standard <string.h> header file).
When some header files are missing, sometimes it is still possible to recover the compilation if the same functionality is defined by some other header files (for example, the standard string manipulation functions are declared by <string.h>, but some systems such as WinCE declare them in <stdlib.h>). In other cases, when the functionality is really not available, then the appropriate replacement implementation need to be provided (for example, isblank() is implemented in <pj/compat/ctype.h> for targets that lack it).Some of the most "problematic" LIBC features will be described below.
Both setjmp() and longjmp() functions are needed to get the exception framework working. The PJLIB exception framework is a TRY/CATCH mechanism for C programs, and is normally used in scanners/parsers to raise syntax error exception and in PJLIB Memory Pool to raise memory allocation failure exception.This functionality is mandatory, and if setjmp()/longjmp() are not available, they MUST be implemented in <pj/compat/setjmp.h>. PJLIB Memory Pool, but this can be changed by creating a new memory pool policy (similar to pj/pool_policy_malloc.c) and specify this policy when creating the pool factory.
PJLIB OS Dependent documentation. PJLIB provides the implementation for Win32 and Posix family OSes, so if the new OS supports one of these OS API, probably we don't need to create a new os_*.c implementation. If this is not the case, then a new os_*.c MUST be created.
Note that depending on the selected porting strategy (described in next chapter), it may be possible to skip creating a full os_*.c implementation for the new OS, and instead just create a dummy implementation just to allow the library to link.
It is perfectly possible to NOT use threads, for example, to run the application on interrupt context, or to use some kind of cooperative multitasking mechanism where a task does not relinguish it's execution time until it has finished with the task. With PJSIP and PJMEDIA, it is also perfectly possible to use only single thread (the main thread) to run everything (including polling for the RTP/RTCP packets), provided that the sound device abstraction supports this. For these cases, we don't need to implement all the PJLIB OS features.
Thread Specific Data
The libraries need to store some data that is specific (and private) for each thread. PJLIB provides this functionality by abstracting the thread local storage (TLS) or thread specific storage API that is system dependent into a uniform thread local storage API.
Even when multithreading is NOT used, thread local storage API must still be implemented in PJLIB. The TLS is used for example by the PJLIB exception framework, since an exception should only be cascaded to the appropriate handler on the same thread only.
In other cases, thread local storage or thread specific storage API may not be available in the target OS. In these cases, application developer would need to implement this functionality via a homegrown mechanism.
PJLIB provides abstractions for synchronization objects such as semaphore, simple mutex, recursive mutex, and read-write mutex. All of these objects are used by the upper layer libraries or application.
The Operating System normally would provide these functionalities in the OS API. But even when it's not available, normally we can emulate most mutex functionalities using semaphore object.
More specificly, PJLIB provides a simple and elegant implementation of read-write mutex for OS that doesn't provide this functionality (such as Win32).
PJ can work in both ANSI and UNICODE systems, and in fact, with PJLIB, it is possible to create a source files that would build correctly in both ANSI and UNICODE variant of the same OS family (such as between Win32 and Windows CE).
The UNICODE support is indicated by PJ_NATIVE_STRING_IS_UNICODE macro, which MUST be defined in the appropriate <pj/compat/os_*.h> file. Some UNICODE utility functions/macros are declared in <pj/unicode.h>.
PJLIB itself (and all other PJ libraries such as PJLIB-UTIL, PJSIP, and PJMEDIA) internally uses ANSI representation for all purposes, and only converts strings to Unicode when they interract with the Unicode operating systems.
PJLIB defines rich abstractions for networking features. These abstractions and the consideration when porting will be described below.Socket API provides abstraction for various socket backends using BSD like API, and the API is declared in <pj/sock.h>, and implementation is provided in pj/sock_bsd.c for BSD socket API. The abstraction has been designed to allow very different socket API backend to be used; for example, efforts are currently under way to implement socket API for Symbian OS directly on top of RSocket interface (instead of using the BSD abstraction).
The PJLIB socket API is needed by the default SIP and media transports. However this may not be needed if application completely rewrites these SIP and media transports.pj_gethostbyname() and pj_gethostip() functions) in <pj/addr_resolv.h>, and implementation for BSD socket backend is provided in pj/addr_resolv_sock.c.
The address resolution functions are needed for two purposes:
PJLIB IOQUEUE (I/O Queue) implements Proactor Pattern for demultiplexing network events. The IOQUEUE can be implemented with multiple backends, such as Windows NT IO Completion Port, Linux epoll, or PJLIB's select() abstraction.
The IOQUEUE is mainly used to poll all sockets for incoming packets, and standard PJSIP and PJMEDIA transports make use of IOQUEUE. Because of this, it is possible to have all sockets in the application (SIP sockets and RTP/RTCP sockets) register to one ioqueue, then poll this ioqueue from a single place (and possibly with a single thread).
However, for platforms that doesn't have socket demultiplexing API (such as selec()), it is possible to NOT implement IOQUEUE altogether, by implementing a custom SIP and media transport.
This section describes the strategies to port PJ libraries into a new platform. There are basically two approaches to porting PJ software into new platform.
The "traditional" way is to completely port PJLIB for the new platform. Once PJLIB has been ported successfully, all other libraries (PJLIB-UTIL, PJSIP, and PJMEDIA) should run on the new platform without modifications, since they depend only to PJLIB.
The second approach is only to partially port PJLIB, but some parts of PJSIP and PJMEDIA will need to be modified.
Both approaches will be described below.
To fully port PJLIB, developer need to provide all the PJLIB features described above. In addition, PJLIB provides quite comprehensive tests (pjlib-test application) to test PJLIB functionalities for the new platform. These test should catch more than 90% of problems that may be had for the new platform, so when pjlib-test reports that everything is okay, there is a very good chance that the porting effort has been successful.
This porting path is recommended for platforms that provide "modern" APIs such as Posix or Windows. If the target platform provides Posix abstraction layer, then this path is recommended, since PJLIB supports Posix API. If this is not available (or desired), then some efforts would be required to rewrite/build some features that are pretty "advanced", such as threading, thread local storage, and network event demultiplexing API (select() and the like).Since not all Operating Systems are able to provide such features, this porting effort may not be feasible. In this case, the second approach below may be used to port PJ software into such platforms.
Many Operating Systems, especially deeply embedded OSes, do not have "advanced" features such as multithreading, thread local storage, network event demultiplexing, and the likes. Or in other cases, these features may not be desired, for design requirements reason or size consideration. For these cases, fully porting PJLIB may not be the best option.
This section attempts to describe alternative way to port PJ software into such platforms. However, readers please be aware that this effort is still being tested, so the procedures described here may not be accurate at all.
This kind of port requires specific application design considerations, which will be described below.
This type of porting is still under investigation, thus there's not much details can be revealed at the moment. More will be written when there's more information available.
If you want to try this path, please contact me to discuss things in more details. Thanks.
The PJLIB Porting Guide in PJLIB's manual also provides some useful info about PJLIB build system and porting PJLIB.