[RoarAudio] Micro HowTo: Writing a simple VUMeter for RoarAudio

Philipp Schafft lion at lion.leolix.org
Thu Mar 18 13:14:29 CET 2010


flum,

amee2k some days ago asked me if it is possible to write a VUMeter for
RoarAudio. Of cause it is possible, and allready done! take a look at
roarvumeter. I also started a GTK version wich is not yet published.

Anyway I want to show to to write a VUMeter for RoarAudio using libroar,
libroardsp (for RMS calculation) and libm (for sqrt()).

First of all we need to include all needed headers:
 #include <roaraudio.h>              /* common RA functions */
 #include <libroardsp/libroardsp.h>  /* for RMS caluclation */
 #include <stdio.h>                  /* for printf()        */
 #include <stdint.h>                 /* for int64_t         */

Next we need to decklar a standard main() function and some vars:
 int main (void) {
  struct roar_connection con;
  struct roar_stream     sinfo, oinfo;
  struct roar_vio_calls  stream, re;
  int ups = 10; /* updates per sec of display */
  int fps;
  ssize_t bps;
  void * data;
  int64_t rms2[2];

  return 0;
 }

I will explain the use of all those vars later in this HowTo.

Of cause the first thing we need to do is to connect the server. This is
done, as allways, by using roar_simple_connect() like this:
 if ( roar_simple_connect(&con, NULL, "amee2kVU") == -1 )
  return 1;

Next we should read the current mixer settings from roard. Why? Because
we can avoid converting by roard and save a lot CPU time this way.

We read the mixer settings like this:
 roar_server_oinfo(&con, &oinfo); // TODO: check error

In a real application you need to handle errors all the functions can
throw. We will skip this for most of the calls in this tutorial to not
make it to complex.

Next we caclulate how many frames per update of display we need to read
from the server in bytes:
 fps  = oinfo.info.rate / ups;
 bps  = fps * 2 * 2;

Next we need to allocate a buffer to hold the data:
 data = roar_mm_malloc(bps); // TODO: free on all errors

(See notes below for errorhandling/freeing of this block of data)

Next we need to open the actual stream. We open a stream in Stereo (2
channel) 16 bit (most common default) with the current sample rate of
the server. A good program should also try to use the number of channels
the server currently uses (at least in case the server runs in mono
mode) and the bits per sample the server uses. As this requires a bit
more complex structure and some if()s we do not handle those cases in
this example. Most functions have a _2_ or _16_ in the names for number
of channels and number of bytes. It is very likely that there are also
corresponding _8_, _32_, whatever functions. just take a look at the
headers.

Opening the stream is done by a call like this:
 if ( roar_vio_simple_new_stream_obj(&stream, &con, &sinfo,
                                     oinfo.info.rate, 2, 16,
                                     ROAR_CODEC_DEFAULT,
                                     ROAR_DIR_MONITOR) == -1 )
  return 2;

Here the stream is opend with server's sample rate, stereo, 26 bits per
sample. We use the default code which is signed int (PCM) ist client
host byte order (we can directly read them as int16_t later).
Using direction MONITOR we get all the allready mixed data from the
core.

Next we open a RE-VIO. Why? Because normaly if you read from or write to
a stream (like every other open file handles on nearly all systems) can
return with less bytes read/written than requested. This is very liekly
to happen on a socket. The RE-VIO will automaticly re-read/re-write as
long as it read/written as much bytes as we requested or EOF or IO error
occurs. On EOF and IO errors our little application should quit (a
bigger application may inform the user and ask if it should reconnect or
something) we get all the requsted bytes from the RE-VIO or we are done.
This makes things much more easy later.

The RE-VIO is opened very simply by:
 roar_vio_open_re(&re, &stream); // TODO: check error

Next we will enter our main loop. We use a while-loop running as long as
we read the correct number of bytes form the stream (all other cases are
EOF or error as stated above):
 while (roar_vio_read(&re, data, bps) == bps) { // TODO: Depending on
                                                // the application you
                                                // should do better
                                                // error handling

Now we have the data in the memory segment pointed to by var data.
We pass the segment to roar_rms2_1_16_2() in order to calculate the RMS
of the signal. The RMS is value of how much power the signal has.
The function name reads like this:
'calculate the squared RMS devided by one for 16 bit integers with two
channels interleved'

Why do the fuction return RMS^2 not RMS?: Some applications only need to
trigger at a static or semi static level. They can square it and
compare. This saves us from a useless call to sqrt(). sqrt() is very
slow (depending on arch and data, may be some hundred instroductions on
some systems!).

The function is called like this:
  roar_rms2_1_16_2(data, fps * 2, rms2); // TODO: check for errors

The multiply with 2 in that call is because we have the number of frames
in fps but the function takes samples. rms2[] will now contain the RMS^2
for both channels.

We want to display the data in percent for the moment. This means we
need to use sqrt() to get the RMS. Also we need to devide it by 2^15 to
get the level scaled to 0..1. Next we multiply with 100 to get percent
from the values. As we need to do that twice (for each channel) I write
a small macro:
 #define rms22pc(x) (sqrt((x))/327.68)

Now we can use printf() to output the values:
  printf("L: %f R: %f\n", rms22pc(rms2[0]), rms22pc(rms2[1]));

This is all we need to have in the main loop. We end it with:
 }

If the program leavs the main loop we need to do the cleanup:
First we close the stream. As we opened a RE-VIO we need to close that
one. It will close the underlying (the stream VIO) automaticly:
 roar_vio_close(&re); // TODO: check error

Next we close the control connection:
 roar_disconnect(&con); // TODO: check error

Finnaly we free the data segment we alloced:
 roar_mm_free(data);


Wasn't that simple? :)

As stated above here a note on freeing the data segment and error
handling:

As long as you exit the program normaly using return from main() or
exit() you can register a callback with atexit(). if you define the var
data global and init with NULL you can write a small callback that frees
it in case of exit. No manual free needed:
 void free_data(void) {
  if ( data != NULL )
   roar_mm_free(data);
  data = NULL;
 }

register with:
 atexit(free_data);

Hope this small HowTo/Tutorial helped someone. At least it was fun to
write :)

-- 
Philipp.
 (Rah of PH2)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 482 bytes
Desc: This is a digitally signed message part
URL: <http://lists.keep-cool.org/pipermail/roaraudio/attachments/20100318/618d520c/attachment.pgp>


More information about the RoarAudio mailing list