InternetRadio, software

Software

The firmware for the InternetRadio can be found here: https://gitlab.com/jvanloov/InternetRadio

Development environment

The development environment is described here: TM4C123 cheat sheet and CC3200 cheat sheet

The build system uses plain Makefiles.

General

There are three main components in the firmware:

  • the menu system;

  • the player;

  • Helix, the MP3 decoder.

Menu system: - main menu: 3 most recent stations, full station list, system menu configuration files favorites.txt / streams.txt

Components

Helix MP3 decoder

The Helix MP3 decoder is part of the Helix DNA multimedia framework. It was developed by RealNetworks; it’s home page is located at: mp3dec

In this project the Helix decoder is built separately from the main code, and included as a library. To build Helix, run the following commands:

$ cd code/helix/
$ make all

Copy the resulting library file the firmware directory:

$ cp code/helix/helix-mp3-obj/libhelixmp3dec.a code/netradio/lib


The Helix encoder included in this project is modified slightly compared to the original.

  • There is a Reset function, which clears all buffers and resets the decoder. It can then be restarted without having to re-initialize it.

    void Reset(MP3DecInfo *dec);

  • The output buffer is replaced by callbacks.

    int MP3Decode(HMP3Decoder hMP3Decoder, unsigned char **inbuf, int *bytesLeft, pcm_buffer_callbacks pbc, int useSize);

    Using the callbacks the decoded output can immediately be written to the output buffer, without needing large intermediate buffers.

Netradio: tests

In code/netradio, the file Makefile-tests (make -f Makefile-tests all) will build a (small) set of tests that were developed for testing a few crucial areas of the code.

After running the build, a set of executables will be placed in code/netradio/bin. Running these one by one will run a set of tests on the code.

Files:

  • src/misc_utils.o
    test/test_utils.o

  • src/circular_data_buffer.o
    test/circular_data_buffer_test.o

  • src/stations_data.o
    src/stations_recent3.o
    test/stations_data_test.o

  • src/streaming_callbacks.o
    test/streaming_callbacks_test.o

Netradio: simulator

In code/netradio, the file Makefile-sim (make -f Makefile-sim all) will build a simulator version of the firmware. This simulator version contains most of the actual firmware, with the exception of the network code and the audio code.

The simulator is used mostly to develop the input/output system and the menu logic. Notably, the button input system and the LCD output system are fully simulated.

The simulator displays 2 panels in the terminal, using the “ncurses” Unix library; it works on Linux and on OSX. The leftmost panel contains the 84x48 LCD screen output, and the rightmost panel contains the output that would be sent to the serial port. The simulated HAL layer (see code/netradio/test/hal_mock.c) translates the lowest level display function calls into ncurses calls. As a result, the higher level display functions run exactly as they will on the device.

The keys “j”,“k”, “l” and “m” correspond to hardware buttons 1 to 4, respectively. In the menu routine, the key “q” corresponds to button 5 and signifies “quit the simulator”. (Button 5 is the left button on the CC3200 launchpad; this button is not available in the InternetRadio hardware.)

Files:

src/main.o
src/user_input.o
src/menu.o
src/circular_data_buffer.o
src/misc_utils.o
src/stations_data.o
src/streaming_callbacks.o
src/station_list_plugin.o
src/system_menu_plugin.o
src/most_recent_plugin.o
src/stations_recent3.o
test/display_mock.o
test/hal_mock.o
test/simplelink_mock.o
test/audio_mock.o test/player_mock.o
test/network_mock.o

Netradio: firmware

In code/netradio, the file Makefile (make all) will build the actual firmware.

Parts

Audio

The audio part, when streaming starts, runs off a timer interrupt. The interrupt is configured for the sample rate of the stream (44.1KHz or 48KHz), and on every timer tick the next sample is read from the audio buffer and sent to the DAC. See source file code/netradio/src/audio.c, second part.

Display

The display code draws in an 84x48 buffer. When a screen is ready (fully drawn, or updated from the previous state) in the buffer, it is sent in full to the LCD. We don’t do partial updates of the screen. This means an update is fairly slow (i.e. low framerate), but in exchange the code for manipulating the screen is fairly simple.

We don’t use the built-in character generator of the LCD (which generates the typical Nokia cell-phone letters). Instead, we draw our own characters in the graphical screen buffer. This allows us to define our own character set with smaller (and variable-width) characters, some application-specific “graphical” characters, and our own string printing routine. Together these permit us to put more information on the screen.

The play loop

The actual play loop is interrupt driven: when we know the stream’s sample rate (44.1KHz or 48KHz), a timer is started that triggers an interrupt with that frequency (file audio.c, function audio_setup).

The interrupt routine will fetch a sample from the audio queue, and send it over SPI to the DAC. The DAC will immediately set its output to the value of this sample. If there is no sample available, the interrupt routine sends a “silence” sample (the midpoint, i.e. 2048) to the DAC.

The DAC has a 12-bit resolution. This means it can represent 4096 different voltage levels. Helix samples are signed (centered around 0) and have 16 bits resolution, so we use a simple transformation to shift the midpoint to 2048, and to reduce the resolution by 4 bits:

transformed_samples_buf[out_index] = (samples_buf[in_index] >> 4) + 2048;

(helix_pcm_handler.c, function return_block_buffer_cb)

The streaming loop

Core

The core streaming loop is located in the play function in player.c. The purpose of this loop is to keep the audio queue sufficiently filled, so that the play loop is never starved for samples.

play():
  Helix: reset decoder
  Loop:
    request data from network
    Helix: try to locate an MP3 sync word in the data
    -> until sync word found
  Start audio out
  While no error & not stop:
    get more data
    Helix: decode the data to the audio queue
    if stream info unknown:
      Helix: get last frame info
      start audio out at required sample rate
  stop audio

The streaming loop works in a “pull” fashion: in the main loop, stream data is requested when needed. The function more_data_cb (streaming_callbacks.c) handles this.

This callback has three sections: - if button 1 is pressed (“stop”), it tells the streaming loop that there is no more data, which will make it stop. This in turn will make the radio return to the main menu. - If the streaming buffer doesn’t have enough data, a network request for more data is done, and if we get more data, it is added to the streaming buffer. - Finally, data is added to the audio buffer for playback.

Refinements

When the loop starts, it will start requesting data from the network. This continues until Helix can extract the stream metadata: number of channels (mono or stereo), sample rate and bitrate. The sample rate must be known before the audio loop can start.

The first few network requests for more data can be jittery; even if the data decodes successfully, we don’t want to start playing audio just yet. Only after decoding several frames, and filling up the audio buffer do we actually want to start the audio output. At that point, we also want to update the screen to show the stream information. During this initial phase, the core loop is already running, but the audio data is discarded until we’ve seen enough successfully decoded frames in a row. The variable subsequent_successful_frames in the play function (player.c) keeps track of this.

The picture below shows the flow of data through the application, using the buffer setup in the application.

Flashing

The firmware on the CC3200 is stored in the file /sys/mcuimg.bin in its flash filesystem. When the MCU is reset, this image is loaded into RAM, and started.

Flashing a new firmware image on the CC3200 is done as follows:

cc3200tool -p /dev/cu.usbserial-cc3101B –reset prompt write_file bin/netradio.bin /sys/mcuimg.bin

where /dev/cu.usbserial-cc3101B is the name of the CC3200 serial port when it is mounted on the computer.

Since we store other information in files on the flash file system, these can be updated, read or erased without touching the firmware image itself:

  • The global list of stations:
    cc3200tool -p /dev/cu.usbserial-cc3101B –reset prompt write_file ../../data/favorites.txt favorites.txt
    cc3200tool -p /dev/cu.usbserial-cc3101B –reset prompt erase_file favorites.txt
    cc3200tool -p /dev/cu.usbserial-cc3101B –reset prompt read_file favorites.txt favorites.txt

  • The selected stations:
    cc3200tool -p /dev/cu.usbserial-cc3101B –reset prompt write_file ../../data/selected.txt selected.txt
    cc3200tool -p /dev/cu.usbserial-cc3101B –reset prompt erase_file selected.txt
    cc3200tool -p /dev/cu.usbserial-cc3101B –reset prompt read_file selected.txt selected.txt

Favorites file format

The favorites file is a sequence of stream names and connection details. For now, the radio doesn’t do DNS resolution, so only IP addresses are supported. Each line in this file can be up to 100 characters long; this determines the maximum length of the URL.

{Station list}
[VRT Radio 1]
37.48.117.1
/radio1-high.mp3
[Studio Brussel]
37.48.117.1
/stubru-high.mp3
[Klara Continuo]
37.48.117.1
/klaracontinuo-high.mp3
...
section name
station name
station server IP
station server stream URL
station name
station server IP
station server stream URL
station name
station server IP
station server stream URL
...


Parts