Porting Considerations
This section describes components that need to be considered when porting PJ
libraries to new platform, especially the embedded or small-footprint systems.
Target
CPU
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.
Instruction Set
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).
Data Types
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.
PJ data types |
Description |
Availability |
Mapped to.. |
pj_int8_t,
pj_uint8_t |
8-bit
signed/unsigned integer |
always
defined |
char, unsigned
char |
pj_int16_t, pj_uint16_t |
16-bit
signed/unsigned integer |
always
defined |
short, unsigned
short |
pj_int32_t,
pj_uint32_t |
32-bit
signed/unsigned integer |
always
defined |
int, unsigned int |
pj_int64_t,
pj_uint64_t |
64-bit
signed/unsigned integer |
only
when PJ_HAS_INT64 is defined (defined by compiler specific header file). |
long
long/int64_t, unsigned long long/uint64_t |
pj_ssize_t,
pj_size_t |
At
least 32 bit, may be 64 bit. |
always
defined |
long,
size_t |
pj_bool_t |
Boolean |
always
defined |
int |
pj_char_t |
Native
character type for given platform. |
always
defined |
char
or wchar_t, depending on the value of PJ_NATIVE_STRING_IS_UNICODE. |
(no PJ data type is defined) |
Floating point |
- |
float |
(no PJ data type is defined) |
Double precision floating point |
- |
double |
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.
Endianness
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
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).
Compiler
and Build System
The use of particular compiler will need to consider the following
issues.
Compiler Specific Features
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.
64bit Integral Data Type Availability
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.
Integrating into the Build System
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.
Availability of <stdarg.h>
Variable number of argument functionality is needed by only one place,
i.e. the logging functions declared in <pj/log.h> header file.
In most cases, <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!
Standard
LIBC Features
Although higher level PJ libraries (such as PJSIP and PJMEDIA)
can work without LIBC (this has been proven in the past
with the porting to Linux kernel mode module target), in most cases
this means that either PJ will abstract LIBC or the similar LIBC
functionalities need to be implemented in PJLIB.
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.
setjmp() and longjmp()
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>.
malloc()
Malloc is the default memory allocation backend for 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.
OS
Features
This definitely would be the hardest part of the porting efforts. Some
of the challenges to be expected are describesd below.
OS Dependent Features
OS dependent features needed by PJ are collected together in
<pj/os.h>, which are documented in 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.
Threading
PJ software works with or without threads, as the libraries
do not create any threads on their own. But when threads are needed,
naturally all synchronization functions must be implemented for the new
OS.
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.
Synchronization Objects
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).
Unicode Support
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.
Networking
Features
PJLIB defines rich abstractions for networking features.
These abstractions and the consideration when porting will be described
below.
Socket API
PJLIB 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.
Address Resolution API
PJLIB provides address resolution API abstraction (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:
- for finding the API address of local host when
creating the SIP and media transports,
- for SIP server resolution procedure, if RFC 3263
server resolution is not used.
If application works strictly with IP addresses only (no hostnames),
then the address resolution API may not be needed.
Socket select() Abstraction
PJLIB provides abstraction for BSD select() system call in
<pj/sock_select.h>, and the implementation in pj/sock_select.c.
The select() abstraction is only used by select IOQUEUE (see below), so
if select IOQUEUE is not used, the select() abstraction is not needed.
IOQUEUE
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.
Porting
Strategies
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.
Fully
Porting PJLIB
The "traditional" path to porting PJ software is to port
the whole PJLIB to the new platform. Since all other libraries and
applications only depend to PJLIB, these upper layer
libraries/applications would most likely be able to run without changes
on the new platform once PJLIB porting is complete.
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.
Partially
Porting PJLIB
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.
Design Considerations
This kind of port requires specific application design
considerations, which will be described below.
- Threading Strategy:
No Threading!
-
To simplify the porting, we would need to disable
threading. By disabling threading, we then could skip implementing the
bulk OS specific codes in pj/os_*.c
(things like threads and synchronization objects). Disabling threading
in this case does not mean that we should not use multitasking at all,
but rather to make sure that no task would be interrupted unless the
task is ready to yield when it thinks that it is safe to do so. This
concept is commonly called cooperative multitasking.
- Create Active
Transports
-
The transport interfaces in both SIP and media stack
was designed around the idea that transports should be active (by
"active", it means that the stack should not need to poll these
transports to get events from the network). But "under the hood",
actually these transports would register their sockets to an IOQUEUE,
and application or SIP endpoint (pjsip_endpoint) would need to
regularly poll the ioqueue to retrieve the network events.
In other words, current framework requires full
implementation of IOQUEUE and PJLIB socket abstraction API, which may
not be available in the target platform. If IOQUEUE and socket API
porting is not desired, then developer would need to rewrite these SIP
and media transports to NOT use IOQUEUE and PJLIB socket. The SIP and
media transports API is quite straightforward; the interface would just
need to register a callback to be called when the stack need to send
packet, and the transport need to call some function in the stack
whenever a packet has arrived.
The transport object itself then may be written in
whatever API provided by the OS. It may even be called from some
interrupt that is triggered when incoming packets arrived.
By implementing the transports this way, there would
not be any need to implement PJLIB's IOQUEUE, select abstraction, and
socket abstraction API.
- Implement
PJLIB Dummy Functions
-
With this approach, many PJLIB functionalities would
not need to be implemented. However, those functions may still have to
be defined (otherwise link error will occur), so dummy implementations
need to be created.
-
More Information
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.
Related
Pages
The PJLIB Porting Guide
in PJLIB's manual also provides some useful info about PJLIB build
system and porting PJLIB.
|
|