Helix Client Porting Guide

Contents

Helix Client Porting Guide

[1Nov04 Revision]

When people initially see the Helix code base, they can be a little overwhelmed and not know where to start. This guide discusses the parts of the code base that typically need porting. A sequence of porting steps is also suggested to help the porting effort show results as quickly as possible.

Porting Steps

The porting effort can be divided into six main steps. These steps are:

Setting up your build environment Implementing platform-specific build system functionality Porting operating system abstraction layer code Porting the platform-independent code Conducting performance analysis Performing optimization

Setting up the build environment is always the first activity that needs to be done. Implementing the platform-specific build functionality always comes next because the build system needs to know how to build code for your target platform.

The next two porting steps should be done in a phased fashion. As you will see, the operating system abstraction code and platform-independent code will be divided up into sets of functionality. Doing this allows the port to have a partially-functioning player before all of the code has been ported.

The performance analysis and optimization steps will likely be done throughout the various porting phases. Real-time media playback is taxing on a target platform's resources; you will more than likely have to do some performance tuning.

Build Environment Setup

Setting up a proper build environment is the first step in a Helix port. In this guide the term "target platform" refers to the operating system to which you are porting Helix. The term "host platform" refers to the operating system used by the target platform's compiler and linker.

For most desktop platforms (Windows/Linux/MacOS) and various other workstation or server platforms (Solaris/IRIX/OSF), the host platform and target platform are the same operating system. In the case of embedded devices, the host platform is usually different from the target platform. The Symbian Helix port is an example of this. The host platform for Symbian is Windows and the target platform is Symbian.

The Helix build system requires that python, cvs, and ssh are installed on the host platform. You can look at the Helix Tools Requirements page to get more details for your host platform. Note that the Helix Tools Requirements page assumes that your target platform is the same as your host platform. If you are cross-compiling, you do not need the compilers listed on that page; instead you will need the compiler for your target platform.

Once you have these programs installed and they are accessible from a command line you are ready to install the build system. The instructions for installing the Helix build system are on the Build System Installation page. You can ignore the information about the compilers if your target platform does not match your host platform. Now that you have the build system installed, you can start implementing the platform specific build system functionality.

Platform-specific Build System Functionality

Before the build system can build any code for a target platform, it must know some details about the target platform's build tools. The build system needs to know how to use the target platform's build tools to compile source files, build libraries, link DLLs, and build program executables. The mechanisms for providing this information to the build system are a little complicated. Luckily there are a lot of examples to look at because Helix has been ported to a number of platforms.

The build system uses the contents of the SYSTEM_ID environment variable to determine the target platform for which you are trying to build. Each target platform is given a unique system ID. For example, win32-i386-vc6, linux-2.2-libc6-sparc, symbian-61-thumb, and symbian-61-emulator are all examples of target platform system IDs. The system ID usually refers to a target operating system, target processor, target processor instruction set, and/or a target tool chain. The system ID name depends on what characteristics make this platform different from other target platforms.

The platform-specific build information for each target platform is stored in ribosome/build/umakecf. If you look in this directory you will see all sorts of .cf files. Most of these files represent target platforms that Helix supports. To determine the filename for the platform-specific build information of a particular target platform, just append a .cf to that platform's system ID. For example, the Symbian 6.1 emulator target platform uses symbian-61-emulator as it's system ID. The platform-specific build information for this platform is contained in symbian-61-emulator.cf.

In many cases, common functionality exists between several platforms. To take advantage of this commonality, "common .cf" files were created. These common files are used by multiple target platforms so that each target platform .cf file does not have to keep redefining the same values. Most new target platforms include some of the common .cf files, then add a small amount of information that is specific only to their platform.

Now that you know how to specify a target platform and where the platform specific information is located, you need to determine what sort of information you'll need to tell the build system about the target platform. The build system uses python classes to accomplish various tasks. The two main classes it uses represent the compiler and the linker. The Compiler class contains information about the name of the compiler and it's default options, include path options, debug options, and release options. The Linker class contains information about the name of the linker, library path options, default options, options for DLL linking, and options for linking program executables.

The build system also uses a global class called "platform" whose values are populated by the platform- specific info code. The platform class contains information about the path separator on the host platform, the name of the makefile tool, the file extensions for various types of build targets (object files, libraries, DLLs, and program executables), the name of the command for copying files, and the name of the command to remove files. Many more things are specified than are listed here.

Usually a new platform will have something in common with at least one of the currently-supported Helix platforms. In that case it is easiest to just copy the existing platform-specific information and change the parts that are different for your platform. When in doubt, you can always ask one of the other Helix developers for help with this part of the port.

Porting the Code

The easiest way to port the Helix code base is to break the large body of code into feature sets. Each set of features is ported and then verified. Once you are sure that those features work as expected, you can move onto the next set of features. At various stages of the port you can build unit tests that verify functionality in isolation.

The porting effort should be divided into the following phases:

The build system verification port The local audio playback port The local audio/video playback port The network playback port The additional functionality port

Before you start porting the code, you should look at the Helix code structure document so you can get a sense of how the code is laid out.

Build System Verification Port

The first phase of a port is the build system verification port. The purpose of this phase is to port a small amount of code so you can verify that the build system can properly build the Helix code base for your target platform. Only the code in common/include, common/runtime, common/unittest, common/dbgtool, common/container, and common/container/test needs to be ported for this phase of the porting operation.

common/include

The common/include directory contains various header files used throughout the Helix code base. Most of the header files in this directory contain Helix interface definitions. The two header files in this directory that require the most attention are hxtypes.h and hxcom.h.

The hxtypes.h file contains the definitions for all of the basic types used by the Helix code base. Typedefs for UINT32, LONG32, INT64, and many other basic types are contained in this file. Proper definition of these types is required to insure that the platform-independent code builds properly.

The hxcom.h file contains all the definitions related to Helix's COM implementation. This header file contains the definition of the INTERFACE, DEFINE_GUID, and many other COM-related macros. The interface definition for IUnknown is also defined in this header file. If the contents of hxcom.h are not valid for the target platform, then the Helix COM implementation will not work properly.

common/runtime

The common/runtime directory contains code that fills in functionality that is missing from a platform's runtime library. Usually this includes things like strcasecmp(), atoi(), and various other functions that are usually considered part of the C runtime library.

common/runtime/pub/hlxclib

The common/runtime/pub/hlxclib directory contains header files that can be used as replacements for standard C library includes. The purpose of the header files is to reduce the amount of #ifdefs in the Helix source code. Throughout the Helix codebase, you'll likely see lines that say something like #include "hlxclib/string.h". This line was a replacement for #include <string.h>. Use the hlxclib header files in the following way:

When a system header is needed for a given C library function, use the hlxclib version first. If there is no hlxclib version, then use a system header with #ifdef pre-processor directives. If there is an hlxclib version of the HEAD, and if it does not contain the function you need, modify the hlxclib version to include the missing function for your given platform.

You should always use the hlxclib version of the system include headers in all cross-platform code if an hlxclib equivalent is available to make future ports easier.

common/unittest

The common/unittest directory contains support functionality for building unit tests. This directory contains platform-independent unit test harness code as well as platform-specific unit test entry point code. The unit test framework code should compile with little effort. If your platform does not support a standard main()-style entry point for a console application, you may need to port the unit test console library code. If you look at common/unittest/ut_console_stub.cpp, it should be pretty clear what the unit test framework code expects. The unit test console library is meant to be linked with unit test code to build a unit test program executable. The console library should have the appropriate entry points to make this happen. If you would like more information about the unit test framework itself, you can look at its documentation.

common/dbgtool

The common/dbgtool directory contains various debug facilities used by the Helix code base. Most of this code is cross platform, but there are a few sections where platform-specific code is needed. The code to handle HX_ASSERT behavior is an area that usually needs porting (HX_ASSERT behavior is modified by editing the HXAssertFailedLine function in hxassert.cpp). If you do not want debug print statements sent to stderr, you will also have to port the debug print code.

common/container

The common/container directory contains various container classes. These classes include maps, lists, strings, buffers, and a few other useful objects. The code should be completely cross platform and require minimal porting effort. The classes are used throughout the Helix code base and must operate properly.

common/container/test

The common/container/test directory contains unit tests that verify the proper operation of some of the classes in common/container. It builds several program executables for the target platform. By reading the README file in the directory, you can find out what tests are built and how to run them on your target platform. All of these tests must pass before you move on to the next porting phase.

Now that all of the directories that need porting have been covered, use the following steps to build the code:

Create a directory for your Helix source tree. Inside that directory, start up the Helix build system by typing build. Set your target to common_container_test and set your profile to helix-client-all-defines. Start your build by selecting the run menu item.

more.gif

For More Information: For a complete description of how to build the client code, see the Helix client quick start guide.

At this point the build system will check out the necessary code and try to build it. Likely something will fail the first time around. The build system will output a build.out file that contains all of the build errors. This file is very helpful for figuring out why the build is failing. It contains all of the command- lines used by the build system and also has the error messages from the compiler. If you run into problems with the compiler or linker command-lines, update the .cf file for your target platform to fix the problem.

Once you have the unit tests built, run them on your target platform. If these tests fail, you will need to debug the tests to figure out why they are failing. Once all the unit tests pass you have successfully completed this phase of the port. You have verified that the build system is able to build program executables for your platform that run properly.

Local Audio Playback Port

Now that you have verified the build system can build code for the target platform, you can start working on getting a basic player building. In this phase, you should only concentrate on getting local file RealAudio playback working. To do this, you will need to port a little more platform-specific code and a large amount of platform-independent code. Because of the amount of code associated with this porting activity, the directories for each piece of code will not be outlined. The source code structure document will give you an idea of what code is in most of the directories.

DLLAccess, HXAsyncTimer, the file abstractions, and CHXAudioDevice are the only operating system abstractions that need to be ported for this phase. Descriptions of these abstractions and their locations are in the Helix OS abstraction document. All of these abstractions should have unit tests that will allow you to verify that your implementation of the abstraction is correct.

The rest of the Helix code should be cross platform and require minimal porting work. Any build problems in the cross-platform code usually relates to picky compilers, header files not being included, or incorrect type conversions. These problems are usually pretty easy to fix. When trying to build this phase of the port, you should set your build system target to splay and your profile to helix-client-local- ra. The build system must successfully build clntcore.dll, cook.dll, rarender.dll, rmfformat.dll, smplfsys.dll, and splay.exe before you can test local audio playback. The following table describes the purpose of each of these files and their build location.


File Source location Purpose
clntcore.dll client/core Client engine
cook.dll datatype/rm/audio/codec/ra8lbr RealAudio8 codec
rarender.dll datatype/rm/audio/renderer RealAudio rendering plug-in
rmfformat.dll datatype/rm/fileformat RealMedia file format plug-in
smplfsys.dll filesystem/local Local file system plug-in
splay.exe clientapps/simpleplayer Simple player executable

Once you have all of these files built, you can test local playback of a RealAudio file. To do this you will first need to copy all of the files listed in the previous table into a single directory on the target platform. Next, copy a RealAudio8 file into the same directory on the target platform. Finally, run the player executable by typing splay.exe followed by the name of RealAudio file. If all goes well, you should hear the file playback on the device. If you don't hear anything or the player crashes, you'll need to debug the code to figure out what is happening.

Local Video Playback Port

The local video playback port proceeds in a very similar fashion to the local audio port. You will need to port a few operating system abstractions and a fair amount of cross-platform code. The operating system abstractions that need to be ported in this phase are HXThread, HXMutex, and the video abstractions. When trying to build this phase of the port, you should set your build system target to splay and your profile to helix-client-local-ra-rv.

When the profile being used is changed, you must do a clean build of the code base since changing profiles causes a corresponding change in the specified feature defines. Before you can test video playback you must make sure that the files mentioned in the local audio playback port still build. The new files that need to build for this phase are shown in the following table.


File Source location Purpose
drvc.dll datatype/rm/video/codec/rv89combo RealVideo 8/9 combo codec back end
rv40.dll datatype/rm/video/codec/rv89combo RealVideo 8/9 combo codec front end
rvxrender.dll datatype/rm/video/renderer RealVideo rendering plug-in
vidsite.dll video/site Video site plug-in
colorcvt.dll video/colorconverter Color conversion

Once you have all of these files building-in addition to those from the local audio playback port section-you should be ready to test local audio/video playback. Copy all the Helix files from the two phases and a RealVideo clip onto the target platform. Try to play the RealVideo file. If all goes well, you should be able to see the video and hear the audio. Depending on the CPU and memory resources of your target platform, your initial playback experience may indicate that additional performance optimization work will be necessary in a later phase; however, unless the problems are severe (audio and or video not playing at all), it is not necessary to complete this performance work before moving to the next phase of porting.

Network Port

note.gif

Note: The Helix interfaces described in the following topic pertains to the IPv4 version of the Helix source code. As of the summer of 2004, the IPv6 version of the Helix source code was implemented only on the HEAD. Branches created prior to the summer of 2004 implement the IPv4 version of the Helix interfaces. Branches created after the summer of 2004 implement the IPv6 version of the Helix network interfaces. If you are using the IPv6 version of the Helix interfaces, the network port using the IPv6 network interfaces is described in Network Port for IPv6.

The network porting phase proceeds much like the previous two porting phases. The only operating system abstractions that need to be ported are the network abstractions. This phase also includes a large body of network-related cross-platform code. This cross-platform code implements the RTSP protocol, the RTP transport, and RealNetwork's proprietary RDT and PNA protocols.

This porting phase differs from the previous two phases in that there is a choice about what network abstraction layer to port. Most of the Helix code base uses interfaces called IHXNetworkServices, IHXTCPSocket, IHXUDPSocket, and IHXResolver to do network I/O. All these interfaces except IHXNetworkServices are asynchronous.

If the target platform's socket API has an asynchronous interface that is relatively close to the Helix interfaces, you may want to implement these interfaces directly. The Symbian and OpenWave ports are examples of platforms that do this. If the target platform has a socket API that is closer to the standard BSD socket API, then it is better to port the lower-level network I/O abstractions. The lower-level abstractions basically wrap the BSD socket interface, and the Helix code uses these wrappers to implement the IHXNetworkServices, IHXTCPSocket, IHXUDPSocket, and IHXResolver interfaces.

When trying to build this porting phase you should set your build system target to splay and your profile to helix-client-local-net-ra-rv. Remember that it is necessary to do a clean rebuild of your source tree due to the change in feature defines caused by the profile change. The sdpplin.dll file is the only new file generated from this port. This file is a plug-in that parses the SDP information sent in RTSP DESCRIBE responses. Most of the networking code that you will be porting will become part of clntcore.dll.

Once you have all of the files from this porting phase and all previous porting phases building again, you are ready to test network playback. Copy all the files into the same directory on the target platform. Now run splay with an RTSP URL that points to an audio-only file. It is better to start with an audio-only file first because it simplifies debugging. It is not likely that everything will work properly on your first try. Some debugging will likely be needed because the networking code is quite complicated and the interface semantics are not always clear. Once you have audio-only clips streaming properly, you can move onto an audio/video clip. Usually audio/video clips will work right away if you already have audio-only clips working. Once you have local file and network playback functionality working, you have a solid player foundation to build off of. Most of the difficult porting work is behind you.

Additional Functionality Port

This porting phase is mainly a catch-all for porting any Helix functionality that is above and beyond basic local file and network playback of RealAudio and RealVideo. The Helix code base has many additional datatypes and features that can be included with the player. Some of these include HTTP cloaking, HTTP playback, RAM file support, SMIL support, MPEG4 video, AAC audio, AMR audio, H.263 video. The work for a majority of these features will only require porting cross-platform code. The easiest way to see what features are available is to look at the profile documentation. You can also look in ribosome/build/umakepf to see what defines are specified for the various other profiles.

Performance Analysis and Optimization

Throughout the various porting stages you will likely have to deal with performance problems on the target platform. The components that affect the player performance the most are the audio and video codecs. These components usually are responsible for taking up most of the CPU time during playback. Because the codecs are so CPU intensive, it is very important to have highly-optimized implementations for your target platform.

For audio-only playback, the audio decoders are the primary CPU user. Coming in second is the work required to pass around all the packets (especially in the case of network playback). This includes allocating memory and doing memory copies.

For audio/video playback, the most CPU-intensive parts of the code are the video decoders and the color converters. Almost all video decoders in Helix provide output in the I420 YUV format. If the device output screen supports I420 natively and no scaling is occurring, then the color converters will use very little CPU time. However, if it is necessary to convert to another color format for output to the frame buffer (for example, RGB565) or if you need to scale the video, then the color converters start to use a lot of CPU time. It is critical that they are tuned as well as possible, using CPU-specific assembly language if possible. If the device supports hardware scaling and/or color conversion, you can alleviate the CPU problem caused by the color converters by modifying the video site (vidsite.dll) to use your hardware scaling and/or color converting features.

For the most part, the Helix client engine is a cooperative multitasking environment. One example is if some components take too much time to do their work, it can adversely affect other components in the system. Whenever you are implementing operating system abstraction code, make sure that you are implementing the functionality in the most efficient way possible. If some components in the system take too much time during network playback, some of the packets may be lost because of an overflow in the operating system UDP buffers.

There is a feature that can be turned on in the client core that allows all network activity to be handled in a separate thread. Doing this avoids the problem of missing network I/O, but it adds more code complexity and may impact performance in other ways.

If you have access to a profiler for your target platform, it can be very helpful in finding "hot spots" in the Helix code. If you don't have a profiler, then the best place to look for performance problems is in the media data path. The data will either start in the file system plug-in or the network code depending on where the data is coming from. Follow the data all the way through the Helix code until it gets sent out to the audio or video device. Look for unnecessary copying of data, slow algorithms for transforming the data, unnecessary allocation activity, or unnecessary code being executed. These things will all contribute to poor performance.

Conclusion

Even though each Helix port is slightly different, a similar methodology can be used for them all. Hopefully this guide has been helpful in provided a little more insight into what it takes to port the Helix code base to a new target platform. If you have any questions or comments about this guide, or would like to make additional recommendations based on your porting experiences, please send them to the porting mailing list.


Copyright © 1995-2004 RealNetworks, Inc. All rights reserved. RealNetworks and Helix are trademarks of RealNetworks.
All other trademarks or registered trademarks are the property of their respective holders.