Howto-BeagleBoard sound, porting madplay

This page describes a simple experiment of porting madplay to BeagleBoard.

System

For the rest of this Howto we assume a BeagleBoard revision C4, a bootable SD/MMC card partition hosting an Ubuntu (Oneiric, Precise, Quantal) kernel image, a valid filesystem (set up on another SD/MMC partition), a presence of a native host compiler and the LLVM 2.9 cross compiler frame with ARM backend being enabled.

Audio out

Many folks report problems with getting something out of the stereo audio jack. Some suspect hardware issues, some other see the kernel being responsible. Me too had no luck when initially trying to play a short wav-sample on a console. However, in most cases the problem can be solved by installing the ALSA driver and adjusting mixer settings properly. This can be tedious, thus, here a configuration file i was using for this experiment, and here the basic procedure to get the audio-out working:

# install kernel sound modules

- modprobe snd_soc_omap_mcbsp

- modprobe snd_soc_twl4030

- modprobe snd-soc-omap3beagle

# configure ALSA using configuration file

- alsactl -f beagle_alsa.conf restore

Having done this, wav-files now should be audible. NOTE, i have used a comparably high-impedance, low-wattage ear-phones coming with my old iPod. Thus, a playback volume limitation was necessary. If you like it loud and dirty, simply adjust 'DAC1 Digital/Analog Fine Playback Volume' and 'DAC2 Digital/Analog Fine Playback Volume' to your needs.

Madplay

Madplay is a high-quality MPEG-audio decoder capable of playing encoded data or decoding it to a file. It supports a wide range of quality/performance settings and provides a basic playback control via keyboard. The project is pretty old already (SourceForge 2004 ?), however it is still fun and presents a nice addition to the console-based apps. Madplay requires two major components, a libmad library containing all the heavy decoding machinery and a libid3tag library resposible for handling ID3 tags encoded into the input data.

Depending on your system, none/some/all might be shipped with the distribution or via additional package installation. In my case, both were already available. However, i initially ran into segfaults when running madplay, gdb session indicated a fault occuring within libmad. Thus, it is recommendable to build both libraries from scratch.

Native GCC build

The building procedure is conventional and straight-forward:

- cd /home/samples/libmad/build

- ../source/configure --prefix=/home/samples/libmad/install

- make all install

NOTE, that libmad in turn requires zlib which you can also install or build natively from scratch. If configure exits neatly but you run into 'target processor does not support thumb mode' during compilation, simply provide a new configure option (again, the source is pretty old and does not provide a configuration for newest processors):

# configure

- if test "$GCC" = yes

- then

-   if test -z "$arch"

-   then

-     case "$host" in

-       armv7*)   arch="-marm" ;;

-       ...

-     esac

-   fi

- fi

NOTE, -marm is a generic/defensive switch to enforce build compatibility, you may want to try other options to tune more aggressively toward Cortex A8.

First test

After building all together, you can now try to play a mp3-file:

- ./madplay TheRamones-IWannaBeSedated.mp3 --verbose --display-time=overall

If you experience segfaults or see warning messages 'object has different size, consider re-linking' there is a conflict between different versions of the same library (shipped with the system and newly custom-built). If there is no error, but you do not hear anything, check your sound configuration. However, even if there is no sound, decoding to a (wav) file should work fine.

If you do hear something, but it doesn't sound like Joey Ramone but more like a pregnant whale...its normal! For high-quality/high bit-depth/sample-rate inputs, this configuration is simply not powerful enough. The decoding rate may be way too low, resulting in a huge frequency shift. Ermm...a Cortex A8 running at 720 MHz is not powerful enough? Come on...

Second test

You can try different optimization options (--disable-debugging when building madplay, CFLAGS="-O[1-5] -march=xxx -mtune=xxx" when building both, libmad and madplay). This for sure will have an effect, but it (also for sure) will still be too marginal to make a real difference. And since i don't assume you want to downsample all of your mp3 stuff to the minimum (even if it is for an experiment), we need to do something more serious. Fortunately, the solution is very simple. Install the libasound2-dev package, configure madplay with --with-alsa and re-build it.

Play the same high-quality file, all fine now. All fine ? Well, it seems that decoding still consumes up to 30% of total CPU time, thats pretty high considering that even a Cortex M3 running at 70 MHz should be (in most cases) sufficient for an "average" decoding.

Oh well, something comes to mind now, isn't there a DSP on the OMAP chip provided for exactly these purposes? Yes, indeed. However, rewriting madplay to utilize C64x+ is kind of a cardiac/liver/lungs transplantation we don't want to bother with for now.

LLVM library build

This is basically a two stage process. First, build assembly files with the clang/opt/llc combo installed on your host (targeting ARM architecture of course). Then, transfer assembler files to the beagle and complete the process by running the native assembler/linker. Since the heavy bits of the decoder machinery are put into a library, we are going to port libmad instead of the player itself.

The first processing step is to create LLVM bitcode from C-files (since clang does not yet support multiple files being specified at the same time, you will have to repeat this for every C-file in the source directory or write a script for convenience), which may look something like this:

- clang -emit-llvm -ccc-host-triple arm-linux-gnueabihf -c $ARGS file.c -o file.llvm.o

Since we are going to create a shared object at the end, ARGS need to contain a compiler switch -fPIC as well as application specific defines -DPIC and -DFPM_DEFAULT. Additional switches are -Wall, -O, -MD, -MP. NOTE, when doing cross compiling (especially on a host differing significantly from the target), you will most certainly need to provide proper headers (and libraries, if you are also going to link on the host). This is usually done by a proper setting of -sysroot/ isysroot (clang). If there are still any definitions missing, correct them by -I (resp. -L for libraries). Further, application specific configuration defines are located in config.h which is generated by configure and also need to be included. I.e. run configure on the beagle to generate this file and then swap onto the host where LLVM processing is done). This of course is not the most elegant way of building, but is more suitable than rewriting the entire configure script just for a quick test.

Then (optionally) run llvm optimizer on each generated IR file:

- opt -O2 -disable-inlining -disable-internalize file.llvm.o -o file.llvm.opt.o

NOTE, we must not eliminate any global symbols since we are not going to recompile the player itself! Thus, use -disable-inlining and -disable-internalize. This should ensure that all symbols available in the source code will be available in the library.

Now, for convenience link all IR files together. For this you can use llvm-link or alternatively llvm-ld. NOTE, when using llvm-ld, optimizations will be done during linking, thus, here too, prevent symbols from vanishing:

- llvm-ld *.llvm.opt.o -link-as-library -disable-inlining -disable-internalize -o libmad.0.15.1b.base.bc

After linking you can invoke opt again, however, this is not strictly necessary, but may make sense, especially for some IPO's:

- opt -O3 -disable-inlining -disable-internalize libmad.0.15.1b.base.bc -o libmad.0.15.1b.opt.bc

Now, finally generate the target assembly:

- llc -march=arm -mattr=+vfp3,+d16 libmad.0.15.1b.opt.bc -float-abi=hard -o libmad.0.15.1b.opt.s

You can enforce a fine-tuning of the code-generation by specifying -mcpu option (and additional target attributes, f.e. -mcpu=cortex-a8, -mattr=v7a, etc.). This however did not work for me in combination with the target assembler/linker. Also, respect target's float handling here. After this step, LLVM code-generation chain is complete, transfer created assembly file to the beagle now. Arrived here, let the s-file first be assembled, create a shared library after that (additionally create a static library as well):

- gcc -c libmad.0.15.1b.opt.s -o libmad.0.15.1b.o

- gcc -shared -Wl,-soname,libmad.so.0 -o libmad.so.0.2.1 libmad.0.15.1b.o

- ar cru libmad.a libmad.0.15.1b.o

- ranlib libmad.a

Alternatively, you can use a linker for the target on your host (if available, CodeSourcery, Angstrom, etc.). Pay attention to link against proper libs ( and their versions!). Finally, transfer created libs (libmad.so.0.2.1, libmad.a) to the lib-directory (f.e. /usr/lib/arm-linux-gnueabihf), call ldconfig and you are done.

Last test

- ./madplay TheRamones-IWannaBeSedated.mp3 --verbose --display-time=overall --tty-control

- time ./madplay TheRamones-MyMyKindOfGirl.mp3 --verbose --display-time=overall --verbose --display-time=overall --output=wave:TheRamones-MyMyKindOfGirl.wav

You can time both variants (GCC vs. LLVM) if you wish, but you will most probably need a finer tool for comparison, since you won't see much of a difference.

That's it!

Complang
Nikolai Kim
Sitemap
Faculty of Informatics
Vienna University of Technology
top | HTML 4.01 | last update: 2012-08-01 (nkim, Nikolai Kim)