Tutorial part 2 (Creating your first plugin):

[ Previous ] [ TOC ] [ Next ]

2.0) Introduction

 In this chapter I will try to describe some basics of writing a plugin, and at the end
you should have some understanding on what the example 2 in the previous chapter
really did (other than giving you gray hair :)

2.1) More in depth look at plugin_main.c from example 2

 As you remember from the previous example, our sourcecode looked like this:
 
file: src/plugin_main.c
#include <xmms/plugin.h>

VisPlugin part2_vp =
{
 NULL,NULL,0,
 "Autotools example 2",
 0,0,
 NULL,
 NULL,
 NULL,
 NULL,
 NULL,
 NULL,
 NULL,
 NULL,
 NULL
};

VisPlugin *get_vplugin_info(void) { return &part2_vp; }

 First line is quite self explanatory, it includes header file, which contains definitions of  all possible
plugin structures. Also the last line is quite easy to understand, because it is mandatory and gets
called when the plugin is loaded. The line has only function, to return VisPlugin structure to xmms,
and therefore quite easy to implement ;)
 If you like, you can now open the include file (usually in /usr/include/xmms/ ) and see what is
inside. But  for those who are too lazy to do that, I have copied VisPlugin structure (that is the
one we are interested in) to below:

typedef struct _VisPlugin
{
 /* Filled in by xmms */
 void *handle;                          

 /* Filled in by xmms */
 char *filename;                       

 /* The session ID for attaching to the control socket */
 int xmms_session;                  

 /* The description that is shown in the preferences box */
 char *description;                  

 /* Numbers of PCM channels wanted in the call to render_pcm */
 int num_pcm_chs_wanted;    

 /* Numbers of freq channels wanted in the call to render_freq */
 int num_freq_chs_wanted;    

 /* Called when the plugin is enabled */
 void (*init)(void);                    

 /* Called when the plugin is disabled */
 void (*cleanup)(void);            

 /* Callback to show the about box */
 void (*about)(void);                

 /* Callback to show the configure box */
 void (*configure)(void);          

 /* Call this with a pointer to your plugin to disable the plugin */
 void (*disable_plugin)(struct _VisPlugin *);

 /* Called when playback starts */
 void (*playback_start)(void); 

 /* Called when playback stops */
 void (*playback_stop)(void);  

 /* Render the PCM data (don't do anything time consuming in here) */
 void (*render_pcm)(gint16 pcm_data[2][512]);

 /* Render the freq data (don't do anything time consuming in here) */
 void (*render_freq)(gint16 freq_data[2][256]);

} VisPlugin;

 Basically this is the structure, that we start filling in the third line of our sourcecode, and
even if it looks crypting right now, the structure is quite easy to fill. Let's start by putting
NULL to first two values and zero to third, reason for this is that XMMS fills them so we
are not interested them at this time. Third number means xmms session number, and usually
it's 0 because it's default session number (note: even if two users had xmms running in the
same time, both would still have session number 0).
  Fourth value is the first that actually does anything in our example, and the purpose for
this string is to tell xmms what name to show in pluginlist. If you are curious to see how it
works, try fiddling with the name in example2, recompile project, overwrite old pluginfile,
and restart xmms.
 Next two lines tell how many channels you want to process in the appropriate callback
routine, and both lines have 3 possible choices. 0 means that you do not need data (and
therefore you do not need callback routine), value 1 means that you need only mono data,
and value 2 means, that you want stereo data. So for example if you want to process only
mono pcm data, your values would be 1,0 and your structure should state callback routine
for pcm data, and NULL for freq data callback.
 If you have absolutely no clue what PCM sample means, you might to do some googling,
or at least read small introduction from here. There are some differences though, one which
might cause some confusion, is that xmms usually does not give full wave data to PCM
callback, so do not rely on it. If you have slow machine, XMMS only gives 512 samples
from time to time, and if you would like to write wav file out of the data.. well it just would
not work.
 The other callback is bit different, because it does not give wave data directly, but what it
does give is fourier transformed representation of wave sample splitted to 256 different
frequency bands. This callback also happens only so many times, that xmms thinks is good
for realtime performance of the main program itself, so on slow machine you might get
fewer samples, than on some fast machine.
 
 But lets continue to the next 9 values, which are all callback routines. As you might have
figured out by now, plugin is based on callbacks. When something interesting happens in
xmms, it calls defined routine (if value != NULL), and plugin can act if it needs to.
 So for example, if you do not have configuration or about dialog in your plugin, you can
define them as NULL, and when you do that, buttons get disabled in plugin page. Also
as I mentioned earlier, if you only process PCM data, you don't need to define routine
for freq data callback, just leave it as NULL.
 One thing that should not be forgotten is that you cannot perform CPU intensive tasks
inside PCM and FREQ callback routines. Well actually you can, but that causes XMMS
interface to get quite jerky, and we would not want that, would we?

2.2) Example 3 [Implementing callbacks to example2]

 In our example 2, all callbacks were defined as NULL, so no matter what happened in
XMMS, nothing got through to our plugin, and that is something we would like to change,
so to make example 2 bit more interactive, we will start by copying content of example2
to example3 folder (cp -R example2 example3). After you have done this, go to example3
folder, and type: ./configure ; make maintainer-clean   which should clean up our
example folder a little bit (although normally you shouldn't use this command).
 Now start by modifying configure.in file (I have marked edited lines with red color, so you
don't have to write everything from the beginning):

file: configure.in
AC_PREREQ(2.58)
AC_INIT(Tutorial part 3,1.0.0,[email@hotmail.com],example3)

AC_CANONICAL_TARGET([])

AM_INIT_AUTOMAKE([example3],[1.0.0])
AC_CONFIG_SRCDIR([src/plugin_main.c])
AM_CONFIG_HEADER([config.h])
AM_DISABLE_STATIC

AC_PROG_CC
AC_PROG_INSTALL
AC_PROG_LIBTOOL

AM_PATH_XMMS(1.2.9,,AC_MSG_ERROR([XMMS >= 1.2.9 not installed]))

AC_CONFIG_FILES([Makefile src/Makefile])
AC_OUTPUT

 Also Makefile.am in src folder need some modifications, so open it, and make the changes that
are marked with red color:

file: src/Makefile.am
lib_LTLIBRARIES = libexample3.la

AM_CFLAGS = -Wall @XMMS_CFLAGS@ @CFLAGS@

libexample3_la_LDFLAGS = -module -avoid-version
libexample3_la_SOURCES = plugin_main.c

 And last but not the least, we will add some printing routines to our example 2, so that we can
see what gets called, and when. So open plugin_main.c in your favourite editor, and do the
following changes (new lines marked as blue, changes marked as red):

file: src/plugin_main.c
#include <xmms/plugin.h>
#include <stdio.h>

static void example_init(void) { printf("Init\n"); }
static void example_cleanup(void) { printf("Cleanup\n"); }
static void example_disable(void) { printf("Disable\n"); }
static void example_play(void) { printf("Start\n"); }
static void example_stop(void) { printf("Stop\n"); }

VisPlugin example_vp =
{
 NULL,NULL,0,
 "Autotools example 3",
 0,0,
 example_init,
 example_cleanup,
 NULL,
 NULL,
 example_disable,
 example_play,
 example_stop,
 NULL,
 NULL
};

VisPlugin *get_vplugin_info(void) { return &example_vp; }

 Now we need to regenerate scripts once again, so run the following commands:

aclocal
libtoolize --force --copy
autoheader
autoconf
automake -a -c

 If you used example2 as base for this new example, none of these commands should have said
anything, as they already had something to begin with. So only thing left for you to do is to compile
program, and see what happens (hint: execute xmms from console to see the output from plugin).

2.3) Example 4 [Adding PCM callback]

 Ok, so we had some fun in example 3 with those 5 callback routines, but in the longer run printing
just 5 values is quite boring. What we need to do next, is to implement PCM callback, and threading
to our plugin, and this is what example 4 is all about.

 Once again we begin this example by copying parts from previous example, and as you now should
know how to do make all the necessary preparations, I will only show what has each build file has
inside:
 
file: configure.in
AC_PREREQ(2.58)
AC_INIT(Tutorial part 4,1.0.0,[email@hotmail.com],example4)

AC_CANONICAL_TARGET([])

AM_INIT_AUTOMAKE([example4],[1.0.0])
AC_CONFIG_SRCDIR([src/plugin_main.c])
AM_CONFIG_HEADER([config.h])
AM_DISABLE_STATIC

AC_PROG_CC
AC_PROG_INSTALL
AC_PROG_LIBTOOL

AM_PATH_XMMS(1.2.9,,AC_MSG_ERROR([XMMS >= 1.2.9 not installed]))

AC_CONFIG_FILES([Makefile src/Makefile])
AC_OUTPUT

file: src/Makefile.am
lib_LTLIBRARIES = libexample4.la

AM_CFLAGS = -Wall @XMMS_CFLAGS@ @CFLAGS@

libexample4_la_LDFLAGS = -module -avoid-version
libexample4_la_SOURCES = plugin_main.c

 As you can see, first two files are almost identical compared to previous examples, so there
is not much to explain here.

file: src/plugin_main.c
#include <xmms/plugin.h>
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

// Functions predefined here
static void example_pcm_callback(gint16 data[2][512]);
static void example_init(void);
static void example_cleanup(void);
static void example_play(void);
static void example_stop(void);

void *example_worker(void *);

// Define global variables
gint16            databuffer[2][512];
static pthread_t    worker_thread;
static pthread_attr_t    worker_attr;
int            worker_running,worker_status,xmms_status;

// Visualization plugin structure
VisPlugin example_vp =
{
 NULL,NULL,0,
 "Autotools example 4",
 2,            // 2 pcm channels
 0,            // 0 freq channels
 example_init,
 example_cleanup,
 NULL,
 NULL,
 NULL,
 example_play,
 example_stop,
 example_pcm_callback,
 NULL
};

// -----------------------
// PCM callback routine
// -----------------------

static void example_pcm_callback(gint16 data[2][512])
{
 // Make a copy of pcm data for worker function
 if(worker_status==0)
 {
    memcpy(databuffer[0],data[0],512);
    memcpy(databuffer[1],data[1],512);
    worker_status=1;
 } else {
  // If for some reason we have new datapacket to
  // process, but our worker is not ready, then
  // print out a warning
  printf("!");
 }
}

// -----------------------
// Worker routine
// -----------------------

void *example_worker(void *arg)
{
 int i;
 double left_val,right_val;
 
 // Thread mainloop
 while(worker_running==1)
 {
  // do work only if xmms is in playback state,
  // and we have something new to process
  if(xmms_status==1 && worker_status!=0)
  {
    // Calculate avarage value
    left_val=0;
    right_val=0;
   
    for(i=0; i<512; i++)
    {
     left_val+=(double)databuffer[0][i];
     right_val+=(double)databuffer[0][i];
    }
   
    left_val/=512.0;
    right_val/=512.0;
   
      printf("(%.2f , %.2f)\n",left_val,right_val);
   
    // acknowledge datapacket
    worker_status=0;       
  }
 
  // Wait about 4ms, and check loop status every 1ms
  // [ note: usleep gives cpu time to multitasking, so
  // if machine is heavily loaded, this loop might
  // take longer than 10ms ]
  for(i=0; i<4; i++)
  {
      usleep(1000);
    if(worker_running==0) pthread_exit(0);
  }
 }
 
 // Exit thread
 pthread_exit(0);
}

// -----------------------
// START / STOP / INIT / CLEANUP
// -----------------------

static void example_init(void)
{
 worker_running=1;        // enable worker loop
 worker_status=0;           // no new datapackets
 xmms_status=0;             // playback stopped

 // Create worker thread
 pthread_attr_init(&worker_attr);
 pthread_attr_setdetachstate(&worker_attr,
     PTHREAD_CREATE_JOINABLE);
 pthread_create(&worker_thread, &worker_attr,
     example_worker, NULL);
 pthread_attr_destroy(&worker_attr);
}

static void example_cleanup(void) {
 // Disable worker loop
 worker_running=0;       
 // Wait a second, for worker to quit
 sleep(1);           
}

static void example_play(void) {
 xmms_status=1;
}

static void example_stop(void) {
 xmms_status=0;
}

// -----------------------
// Function required by xmms
// -----------------------
VisPlugin *get_vplugin_info(void) { return &example_vp; }


 But, what has changed quite radically is this file, as it has grown quite much since last
example. Most of the code should be quite obious, basically when we initialize plugin
for the first time, we create worker process using pthread, and after that we control
it by using 3 variables.
 As long as 'worker_running'-variable is set to 1, our worker thread does not quit and
tries to process pcm datapackets. 'worker_status'-variable is a signal for worker to
start processing new data, and also to let pcm render function know, if our worker is
ready to receive new data for processing. 'xmms_status' variable is just to keep track of
xmms playback status (0 = stopped, 1 = playing).
 Output (avarage of 512 16-bit signed pcm samples) from plugin goes directly to console
window, so to see the output, you should open xmms from terminal program (also note
that this prints new line about 100 times per second).