This blog post presents the content from my linux.conf.au 2013 presentation Droids that talk: Pairing Codec2 and Android, where I presented some background on radio theory, and followed by a discussion of the software components required to run Codec2 and Fdmdv2 on Android, via a USB sound card.
Codec2 a digital voice codec for communication below 2400 bits per second, developed by local voice codec guru David Rowe. It is fully open – free software, as in both beer and speech sense. He as also created a modem designed for carrying Codec2 over HF channels, called Fdmdv2. David and his co-contributors have combined Codec2 and Fdmdv2 to create FreeDV, a free software digital voice application for communicating over HF radio using your Linux, Mac or PC.
In addition to my FOSS hobbies, I’ve been getting involved in amateur radio these past few years. This began with meeting Mark in the final year of my degree, moved into my time spent playing with balloons as part of Project Horus, and now has continued with FreeDV.
As technology progresses, more complex systems can be built and understood with the tools we have. Sophisticated DSP and SDR are within the reach of the linux.conf.au attendee skill set. DSP is digital signal processing; a field that moves signal processing from physical circuits to software that runs on a computer. It is relatively new, as it was only in the 80s that digital logic became fast enough to run the software in real time. SDR is software defined radio, where DSP techniques are applied to the building of radios. The following information describes the building blocks for using DSP and SDR to implement a digital voice over radio system.
In studying for my Engineering degree, I learnt a bit about how radios work. Of interest for FreeDV is the use of a direct conversion receiver Put simply, this kind of receiver uses the difference between a local oscillator and an amplified incoming HF signal to produce an image of the HF signal at baseband. Once amplified this baseband signal is suited for capture by a sound card, in order to be decoded and tuned using Software Defined Radio techniques. Using these simple building blocks a DSB receiver can be constructed for receiving FreeDV or SSB analog voice over the air.
Such a radio is described in my building a double sideband receiver blog post. The completed radio is show below, with the major stages compared to the block diagram above.
Now that the HF signal present in the computer and ready to be processed, the software must be designed. The codec and modem is implemented in C; in order to save effort in re-implementing in Java and staying up to date with changes, it was decided to use the C libraries directly, and interface them with the Java’s foreign function interface; Java Native Interface. The Android team provide a cross complier for the supported applications called the Native Development Kit (NDK).
Android Audio Input
Android handsets have a connector for microphones, however, they are not suitable as a line-in in the same way as a PC sound input, as the connector pins vary between phone manufacturers as does the specification for the impedance between pins in order to detect the microphone. Instead, it was decided to use a USB Audio Class (UAC) soundcard device digitise the baseband radio signal.
Android runs the Linux kernel, however, unlike a desktop Linux operating system, it does not include a driver for supporting UAC devices. A user space driver for supporting the UAC device was written, using the c libusb library. This sets up an isochronous transfer from the appropriate descriptor to get 16-bit PCM audio at 48kHz delivered to a callback. The configuration is currently hardcoded for the TI PCM2900C device; this will be made generic in time.
Another complication that Android brings is the security architecture An application can only access a USB device if it has requested permission through the USB host API. This is only provided on Android 3.1 (API 12), which means pre-4.0 devices are not supported. This compromise is acceptable, as in general older devices lack the CPU cycles to run the application. A modified version of libusb that requests permission on open, and is buildable by the Android NDK toolchain, can be found on github. Thanks to Роман Донченко for releasing this work!
The C code is all built using the NDK build system. Each component is built as a separate shared library, with the JNI code initialising each of the modules below it and registering handles for the appropriate Java callback functions. The USB callback receives the sound samples from the radio, and calls into a processing loop that is shared with the FreeDV desktop software. When enough samples to decode an entire frame, this loop uses the modem to decode a frame, which itself passes the bits to the codec if the modem is in sync.
The modem depends on the quality of the HF channel; when the demodulation side of the modem cannot sync, no data can be recovered. Synchronisation state, as well as estimates of the timing and frequency offset of the recovered signal is passed to the Java side of the app to be displayed. When the modem is in sync, the decoded phase information is also passed to Java so it can be plotted in a scatter plot. This plot allows the user to adjust their radio’s output levels to ensure a good spread, as well as observe the effects of interference on the received data.
In Android, there are no built-in widgets for drawing graphs of any kind. There does exist a library called GraphView, which provides various types of classes. As I had familiarity with the line graph class, I used this for the timing and frequency offset graphs. This required hacking GraphView a fair bit, as it did not at the time support the graphing of real-time data (since then it has gained this functionality).
I decided to write my own scatter graph class, in order to learn about drawing on Android. It inherits from View, with data collected as it comes from the modem into a LinkedList of fixed size, and invalidate() is called so that the system knows to re-draw the graph when the UI thread is ready. The onDraw() call finds the largest points, scales and filters them, and draws circles for each point to a Canvas. It was easy enough to do drawing in Android by following the basic shape drawing tutorial, despite appearing complex when reading about it. I was worried about performance; should I be scaling the points when inserted, so they don’t happen when drawing? Or does that mean that I would be iterating over the list to update the scale more than once between onDraws, and therefore do unnecessary work? These are still good questions to answer, but for now, it Just Works.
Native Code Optimisation
The native code is built with
-O2 -ffast-math flags for speed, as well as NEON support enabled through the NDK’s mechanism. There were optimisations provided to the codec2 mailing list some time ago, including using the float instead of double precision variant of the maths functions, and I incorporated some of these in the copy of the FreeDV software I used. It is not clear if these performance optimisations are necessary in order for real-time performance; some benchmarking is required. For now the NEON support means that the application will not run on all platforms, as there is no run-time detection performed.
In the near future, I plan to add a waterfall display of the received spectrum, and allow tuning within this range. I will also add support for generic USB Audio Class devices, as well as analog input for those who have line-in ability on their Android devices.
The source code can be found on Github. Please download, compile, code-review, hack, and test. Any feedback is welcome.
Thanks to David and Mark for your help with all of the hacking, both hardware and software, to Kristina for your help in preparing my talk and taking the photos of me presenting that I’ve used on this blog post.