Rework PCM buffer
* Linked list instead of static array buffer pointers
* Variable sized chunks
* Improved mix handling
* Reduction in duplicated code
* Reduced IRAM usage w/o sacrificing performance
* Converted to almost entirely unsigned math
* Add pause function to reduce pcm_* exposure to playback.

This WILL break playback on the iPod until linuxstb makes a followup commit.


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@8612 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/codecs.h b/apps/codecs.h
index 00f0f64..cf0324b 100644
--- a/apps/codecs.h
+++ b/apps/codecs.h
@@ -128,8 +128,8 @@
     void* (*get_codec_memory)(long *size);
     /* Insert PCM data into audio buffer for playback. Playback will start
        automatically. */
-    bool (*pcmbuf_insert)(char *data, long length);
-    bool (*pcmbuf_insert_split)(void *ch1, void *ch2, long length);
+    bool (*pcmbuf_insert)(const char *data, size_t length);
+    bool (*pcmbuf_insert_split)(const void *ch1, const void *ch2, size_t length);
     /* Set song position in WPS (value in ms). */
     void (*set_elapsed)(unsigned int value);
     
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index e7067cc..4c36eaa 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -209,7 +209,7 @@
 extern int filebufused;
 extern int track_count;
 
-static int ticks, boost_ticks;
+static unsigned int ticks, boost_ticks;
 
 void dbg_audio_task(void)
 {
@@ -225,7 +225,8 @@
     int button;
     int line;
     bool done = false;
-    int bufsize = pcmbuf_get_bufsize();
+    size_t bufsize = pcmbuf_get_bufsize();
+    int pcmbufdescs = pcmbuf_descs();
 
     ticks = boost_ticks = 0;
 
@@ -239,6 +240,12 @@
         button = button_get_w_tmo(HZ/5);
         switch(button)
         {
+            case SETTINGS_NEXT:
+                audio_next();
+                break;
+            case SETTINGS_PREV:
+                audio_prev();
+                break;
             case SETTINGS_CANCEL:
                 done = true;
                 break;
@@ -248,8 +255,8 @@
         
         lcd_clear_display();
 
-        snprintf(buf, sizeof(buf), "pcm: %d/%d",
-                 bufsize-(int)audiobuffer_free, bufsize);
+        snprintf(buf, sizeof(buf), "pcm: %7ld/%7ld",
+                 bufsize-audiobuffer_free, bufsize);
         lcd_puts(0, line++, buf);
 
         /* Playable space left */
@@ -257,7 +264,7 @@
                   bufsize-audiobuffer_free, HORIZONTAL);
         line++;
 
-        snprintf(buf, sizeof(buf), "codec: %d/%d", filebufused, filebuflen);
+        snprintf(buf, sizeof(buf), "codec: %8d/%8d", filebufused, filebuflen);
         lcd_puts(0, line++, buf);
 
         /* Playable space left */
@@ -265,17 +272,21 @@
                   filebufused, HORIZONTAL);
         line++;
 
-        snprintf(buf, sizeof(buf), "track count: %d", track_count);
+        snprintf(buf, sizeof(buf), "track count: %2d", track_count);
         lcd_puts(0, line++, buf);
 
-        snprintf(buf, sizeof(buf), "cpu freq: %dMHz",
+        snprintf(buf, sizeof(buf), "cpu freq: %3dMHz",
                  (int)((FREQ + 500000) / 1000000));
         lcd_puts(0, line++, buf);
 
-        snprintf(buf, sizeof(buf), "boost ratio: %d%%",
+        snprintf(buf, sizeof(buf), "boost ratio: %3d%%",
                  boost_ticks * 100 / ticks);
         lcd_puts(0, line++, buf);
 
+        snprintf(buf, sizeof(buf), "pcmbufdesc: %2d/%2d",
+                pcmbuf_used_descs(), pcmbufdescs);
+        lcd_puts(0, line++, buf);
+        
         lcd_update();
     }
 
diff --git a/apps/dsp.c b/apps/dsp.c
index e4d28bd..1106f02 100644
--- a/apps/dsp.c
+++ b/apps/dsp.c
@@ -213,7 +213,7 @@
  * consume. Note that for mono, dst[0] equals dst[1], as there is no point
  * in processing the same data twice.
  */
-static int convert_to_internal(char* src[], int count, long* dst[])
+static int convert_to_internal(const char* src[], int count, long* dst[])
 {
     count = MIN(SAMPLE_BUF_SIZE / 2, count);
 
@@ -773,7 +773,7 @@
  * pointers, one for each audio channel. Returns number of bytes written to
  * dest.
  */
-long dsp_process(char* dst, char* src[], long size)
+long dsp_process(char* dst, const char* src[], long size)
 {
     long* tmp[2];
     long written = 0;
diff --git a/apps/dsp.h b/apps/dsp.h
index 364c8d8..7e3acac 100644
--- a/apps/dsp.h
+++ b/apps/dsp.h
@@ -47,7 +47,7 @@
     DSP_CROSSFEED
 };
 
-long dsp_process(char *dest, char *src[], long size);
+long dsp_process(char *dest, const char *src[], long size);
 long dsp_input_size(long size);
 long dsp_output_size(long size);
 int dsp_stereo_mode(void);
diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c
index 57058a3..cfb297e 100644
--- a/apps/pcmbuf.c
+++ b/apps/pcmbuf.c
@@ -36,32 +36,32 @@
 #include "audio.h"
 #include "dsp.h"
 
-#define CHUNK_SIZE           32768
-/* Must be a power of 2 */
-#define NUM_PCM_BUFFERS      128
-#define NUM_PCM_BUFFERS_MASK (NUM_PCM_BUFFERS - 1)
-/* Watermark level at 1s. */
 #define PCMBUF_WATERMARK     (NATIVE_FREQUENCY * 4 * 1)
 
-/* Audio buffer related settings. */
-static long pcmbuf_size = 0;      /* Size of the PCM buffer. */
-static char *audiobuffer;
-static long audiobuffer_pos;      /* Current audio buffer write index. */
-long audiobuffer_free IDATA_ATTR; /* Amount of bytes left in the buffer. */
-static long audiobuffer_fillpos;  /* Amount audiobuffer_pos will be increased.*/
-static char *guardbuf;
+/* Size of the PCM buffer. */
+static size_t pcmbuf_size IDATA_ATTR = 0;
 
-static void (*pcmbuf_event_handler)(void);
+static char *audiobuffer IDATA_ATTR;
+/* Current audio buffer write index. */
+static size_t audiobuffer_pos IDATA_ATTR;
+/* Amount of bytes left in the buffer. */
+size_t audiobuffer_free IDATA_ATTR;
+/* Amount audiobuffer_pos will be increased.*/
+static size_t audiobuffer_fillpos IDATA_ATTR;
+static char *guardbuf IDATA_ATTR;
+
+static void (*pcmbuf_event_handler)(void) IDATA_ATTR;
+static void (*position_callback)(size_t size) IDATA_ATTR;
 
 /* Crossfade related. */
-static int crossfade_mode;
-static bool crossfade_enabled;
-static bool crossfade_active;
-static bool crossfade_init;
-static int crossfade_pos;
-static int crossfade_rem;
+static int crossfade_mode IDATA_ATTR;
+static bool crossfade_enabled IDATA_ATTR;
+static bool crossfade_active IDATA_ATTR;
+static bool crossfade_init IDATA_ATTR;
+static size_t crossfade_pos IDATA_ATTR;
+static size_t crossfade_rem IDATA_ATTR;
 
-static struct mutex pcmbuf_mutex;
+static struct mutex pcmbuf_mutex IDATA_ATTR;
 
 /* Crossfade modes. If CFM_CROSSFADE is selected, normal
  * crossfader will activate. Selecting CFM_FLUSH is a special
@@ -73,32 +73,44 @@
     CFM_FLUSH
 };
 
-static int crossfade_fade_in_amount;
-static int crossfade_fade_in_rem;
+static size_t crossfade_fade_in_amount IDATA_ATTR;
+static size_t crossfade_fade_in_rem IDATA_ATTR;
+
 
 /* Structure we can use to queue pcm chunks in memory to be played
  * by the driver code. */
 struct pcmbufdesc
 {
     void *addr;
-    int size;
+    size_t size;
+    struct pcmbufdesc* link;
     /* Call this when the buffer has been played */
     void (*callback)(void);
-} pcmbuffers[NUM_PCM_BUFFERS] IDATA_ATTR; /* Do we really need IRAM for this? */
+};
 
-static int pcmbuf_read_index;
-static int pcmbuf_write_index;
-static int pcmbuf_unplayed_bytes IDATA_ATTR;
-static int pcmbuf_mix_used_bytes;
-static int pcmbuf_watermark;
-static void pcmbuf_under_watermark(int bytes_left);
-static int pcmbuf_num_used_buffers(void);
-static void (*position_callback)(int size);
-static int last_chunksize;
-static long mixpos = 0;
+static size_t pcmbuf_descsize;
+static struct pcmbufdesc *pcmbuf_read IDATA_ATTR;
+static struct pcmbufdesc *pcmbuf_read_end IDATA_ATTR;
+static struct pcmbufdesc *pcmbuf_write IDATA_ATTR;
+static struct pcmbufdesc *pcmbuf_write_end IDATA_ATTR;
+static size_t last_chunksize IDATA_ATTR;
+static size_t pcmbuf_unplayed_bytes IDATA_ATTR;
+static size_t pcmbuf_mix_used_bytes IDATA_ATTR;
+static size_t pcmbuf_watermark IDATA_ATTR;
+static size_t mixpos IDATA_ATTR = 0;
 static bool low_latency_mode = false;
 
-#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+/* Helpful macros for use in conditionals this assumes some of the above
+ * static variable names */
+#define NEED_FLUSH(position) \
+    (audiobuffer_fillpos > PCMBUF_TARGET_CHUNK || position >= pcmbuf_size)
+#define LOW_DATA(quarter_secs) \
+    (pcmbuf_unplayed_bytes < NATIVE_FREQUENCY * quarter_secs)
+
+static void pcmbuf_flush_audio(void);
+static void pcmbuf_under_watermark(void);
+
+#if defined(HAVE_ADJUSTABLE_CPU_FREQ) && !defined(SIMULATOR)
 static bool boost_mode;
 
 void pcmbuf_boost(bool state)
@@ -106,7 +118,7 @@
     static bool boost_state = false;
     
     if (crossfade_init || crossfade_active || boost_mode)
-        return ;
+        return;
         
     if (state != boost_state) {
         cpu_boost(state);
@@ -122,106 +134,129 @@
 }
 #endif
 
-static int pcmbuf_num_used_buffers(void)
+#define CALL_IF_EXISTS(function, args...) if (function) function(args)
+/* This function has 2 major logical parts (separated by brackets both for
+ * readability and variable scoping).  The first part performs the
+ * operastions related to finishing off the last buffer we fed to the DMA.
+ * The second part performs the operations involved in sending a new buffer 
+ * to the DMA.  Finally the function checks the status of the buffer and
+ * boosts if necessary */
+static void pcmbuf_callback(unsigned char** start, size_t* size) ICODE_ATTR;
+static void pcmbuf_callback(unsigned char** start, size_t* size)
 {
-    return (pcmbuf_write_index - pcmbuf_read_index) & NUM_PCM_BUFFERS_MASK;
+    {
+        struct pcmbufdesc *pcmbuf_current = pcmbuf_read;
+        /* Take the finished buffer out of circulation */
+        pcmbuf_read = pcmbuf_current->link;
+
+        {
+            size_t finished_size = last_chunksize;
+            audiobuffer_free += finished_size;
+
+            /* The buffer is finished, call the callback functions */
+            CALL_IF_EXISTS(position_callback, finished_size);
+        }
+        CALL_IF_EXISTS(pcmbuf_current->callback);
+
+        /* Put the finished buffer back into circulation */
+        pcmbuf_write_end->link = pcmbuf_current;
+        pcmbuf_write_end = pcmbuf_current;
+    }
+
+    {
+        /* Send the new buffer to the pcm */
+        struct pcmbufdesc *pcmbuf_new = pcmbuf_read;
+        size_t *realsize = size;
+        unsigned char** realstart = start;
+        if(pcmbuf_new)
+        {
+            size_t current_size = pcmbuf_new->size;
+            pcmbuf_unplayed_bytes -= current_size;
+            *realsize = current_size;
+            last_chunksize = current_size;
+            *realstart = pcmbuf_new->addr;
+        }
+        else
+        {
+            /* No more buffers */
+            last_chunksize = 0;
+            *realsize = 0;
+            *realstart = NULL;
+            CALL_IF_EXISTS(pcmbuf_event_handler);
+        }
+    }
+    
+    if(pcmbuf_unplayed_bytes <= pcmbuf_watermark) pcmbuf_under_watermark();
 }
 
-static void pcmbuf_callback(unsigned char** start, long* size) ICODE_ATTR;
-static void pcmbuf_callback(unsigned char** start, long* size)
+void pcmbuf_set_position_callback(void (*callback)(size_t size))
 {
-    struct pcmbufdesc *desc = &pcmbuffers[pcmbuf_read_index];
-
-    if (position_callback) {
-        position_callback(last_chunksize);
-    }
-    
-    pcmbuf_unplayed_bytes -= last_chunksize;
-    audiobuffer_free += last_chunksize;
-        
-    if(desc->size == 0)
-    {
-        /* The buffer is finished, call the callback function */
-        if(desc->callback)
-            desc->callback();
-
-        /* Advance to the next buffer */
-        pcmbuf_read_index = (pcmbuf_read_index + 1) & NUM_PCM_BUFFERS_MASK;
-        desc = &pcmbuffers[pcmbuf_read_index];
-    }
-    
-    if(pcmbuf_num_used_buffers())
-    {
-    
-        *start = desc->addr;
-        *size = desc->size;
-
-        /* Update the buffer descriptor */
-        desc->addr += desc->size;
-        desc->size = 0;
-    }
-    else
-    {
-        /* No more buffers */
-        *size = 0;
-        if (pcmbuf_event_handler)
-            pcmbuf_event_handler();
-    }
-
-    last_chunksize = *size;
-
-    if(pcmbuf_unplayed_bytes <= pcmbuf_watermark)
-    {
-        pcmbuf_under_watermark(pcmbuf_unplayed_bytes);
-    }
-}
-
-void pcmbuf_set_position_callback(void (*callback)(int size)) {
     position_callback = callback;
 }
 
-static void pcmbuf_set_watermark_bytes(int numbytes)
+static void pcmbuf_set_watermark_bytes(size_t numbytes)
 {
     pcmbuf_watermark = numbytes;
 }
 
-bool pcmbuf_add_chunk(void *addr, int size, void (*callback)(void))
+/* This is really just part of pcmbuf_flush_fillpos, but is easier to keep
+ * in a separate function for the moment */
+static inline void pcmbuf_add_chunk(void)
 {
-    /* We don't use the last buffer, since we can't see the difference
-       between the full and empty condition */
-    if(pcmbuf_num_used_buffers() < (NUM_PCM_BUFFERS - 2))
-    {
-        pcmbuffers[pcmbuf_write_index].addr = addr;
-        pcmbuffers[pcmbuf_write_index].size = size;
-        pcmbuffers[pcmbuf_write_index].callback = callback;
-        pcmbuf_write_index = (pcmbuf_write_index+1) & NUM_PCM_BUFFERS_MASK;
-        pcmbuf_unplayed_bytes += size;
-        pcmbuf_mix_used_bytes = MAX(0, pcmbuf_mix_used_bytes - size);
-        return true;
+    register size_t size = audiobuffer_fillpos;
+    /* Grab the next description to write, and change the write pointer */
+    register struct pcmbufdesc *pcmbuf_current = pcmbuf_write;
+    pcmbuf_write = pcmbuf_current->link;
+    /* Fill in the values in the new buffer chunk */
+    pcmbuf_current->addr = &audiobuffer[audiobuffer_pos];
+    pcmbuf_current->size = size;
+    pcmbuf_current->callback = pcmbuf_event_handler;
+    pcmbuf_current->link = NULL;
+    /* This is single use only */
+    pcmbuf_event_handler = NULL;
+    if (pcmbuf_read) {
+        /* If there is already a read buffer setup, add to it */
+        pcmbuf_read_end->link = pcmbuf_current;
+    } else {
+        /* Otherwise create the buffer */
+        pcmbuf_read = pcmbuf_current;
     }
+    /* This is now the last buffer to read */
+    pcmbuf_read_end = pcmbuf_current;
+
+    /* Update bytes counters */
+    pcmbuf_unplayed_bytes += size;
+    if (pcmbuf_mix_used_bytes > size)
+        pcmbuf_mix_used_bytes -= size;
     else
-        return false;
+        pcmbuf_mix_used_bytes = 0;
+
+    audiobuffer_pos += size;
+    if (audiobuffer_pos >= pcmbuf_size)
+        audiobuffer_pos = 0;
+
+    audiobuffer_fillpos = 0;
 }
 
-static void pcmbuf_under_watermark(int bytes_left)
+static void pcmbuf_under_watermark(void)
 {
     /* Fill audio buffer by boosting cpu */
     pcmbuf_boost(true);
-    if (bytes_left <= CHUNK_SIZE * 2 && crossfade_mode != CFM_FLUSH)
+    /* Disable crossfade if < .5s of audio */
+    if (LOW_DATA(2) && crossfade_mode != CFM_FLUSH)
         crossfade_active = false;
 }
 
-void pcmbuf_add_event(void (*event_handler)(void))
+void pcmbuf_set_event_handler(void (*event_handler)(void))
 {
     pcmbuf_event_handler = event_handler;
 }
 
 unsigned int pcmbuf_get_latency(void)
 {
-    int latency = (pcmbuf_unplayed_bytes + pcm_get_bytes_waiting()) 
-        * 1000 / 4 / 44100;
-    
-    return latency<0?0:latency;
+    /* Be careful how this calculation is rearranted, it's easy to overflow */
+    size_t bytes = pcmbuf_unplayed_bytes + pcm_get_bytes_waiting();
+    return bytes / 4 / (NATIVE_FREQUENCY/1000);
 }
 
 void pcmbuf_set_low_latency(bool state)
@@ -231,19 +266,17 @@
 
 bool pcmbuf_is_lowdata(void)
 {
-    if (!pcm_is_playing() || pcm_is_paused() || crossfade_init || crossfade_active)
+    if (!pcm_is_playing() || pcm_is_paused() ||
+            crossfade_init || crossfade_active)
         return false;
-
-    /* 0.5s. */
-    if (pcmbuf_unplayed_bytes < NATIVE_FREQUENCY * 4 / 2)
-        return true;
-        
-    return false;
+    
+    /* 0.5 seconds of buffer is low data */
+    return LOW_DATA(2);
 }
 
 bool pcmbuf_crossfade_init(bool manual_skip)
 {
-    if (pcmbuf_size - audiobuffer_free < CHUNK_SIZE * 8
+    if (pcmbuf_unplayed_bytes < PCMBUF_TARGET_CHUNK * 8
         || !pcmbuf_is_crossfade_enabled()
         || crossfade_active || crossfade_init || low_latency_mode) {
         pcmbuf_flush_audio();
@@ -267,18 +300,19 @@
 void pcmbuf_play_stop(void)
 {
     mutex_lock(&pcmbuf_mutex);
-    
     /** Prevent a very tiny pop from happening by muting audio
      *  until dma has been initialized. */
     pcm_mute(true);
     pcm_play_stop();
     pcm_mute(false);
     
-    last_chunksize = 0;
     pcmbuf_unplayed_bytes = 0;
     pcmbuf_mix_used_bytes = 0;
-    pcmbuf_read_index = 0;
-    pcmbuf_write_index = 0;
+    if (pcmbuf_read) {
+        pcmbuf_write_end->link = pcmbuf_read;
+        pcmbuf_write_end = pcmbuf_read_end;
+        pcmbuf_read = pcmbuf_read_end = NULL;
+    }
     audiobuffer_pos = 0;
     audiobuffer_fillpos = 0;
     audiobuffer_free = pcmbuf_size;
@@ -291,19 +325,53 @@
     mutex_unlock(&pcmbuf_mutex);
 }
 
-void pcmbuf_init(long bufsize)
+int pcmbuf_used_descs(void) {
+    struct pcmbufdesc *pcmbuf_temp = pcmbuf_read;
+    unsigned int i = 0;
+    while (pcmbuf_temp) {
+        pcmbuf_temp = pcmbuf_temp->link;
+        i++;
+    }
+    return i;
+}
+
+int pcmbuf_descs(void) {
+    return pcmbuf_size / PCMBUF_MINAVG_CHUNK;
+}
+
+size_t get_pcmbuf_descsize(void) {
+    return pcmbuf_descsize;
+}
+    
+static void pcmbuf_init_pcmbuffers(void) {
+    struct pcmbufdesc *next = pcmbuf_write;
+    next++;
+    pcmbuf_write_end = pcmbuf_write;
+    while ((void *)next < (void *)audiobufend) {
+        pcmbuf_write_end->link=next;
+        pcmbuf_write_end=next;
+        next++;
+    }
+}
+        
+/* Initialize the pcmbuffer the structure looks like this:
+ * ...CODECBUFFER|---------PCMBUF---------|GUARDBUF|DESCS| */
+void pcmbuf_init(size_t bufsize)
 {
     mutex_init(&pcmbuf_mutex);
     pcmbuf_size = bufsize;
-    audiobuffer = (char *)&audiobuf[(audiobufend - audiobuf) - 
-                                    pcmbuf_size - PCMBUF_GUARD];
+    pcmbuf_descsize = pcmbuf_descs()*sizeof(struct pcmbufdesc);
+    audiobuffer = (char *)&audiobuf[(audiobufend - audiobuf) -
+        (pcmbuf_size + PCMBUF_FADE_CHUNK + pcmbuf_descsize)];
     guardbuf = &audiobuffer[pcmbuf_size];
+    pcmbuf_write = (struct pcmbufdesc *)(&guardbuf[PCMBUF_FADE_CHUNK]);
+    pcmbuf_init_pcmbuffers();
     position_callback = NULL;
     pcmbuf_event_handler = NULL;
     pcmbuf_play_stop();
 }
 
-long pcmbuf_get_bufsize(void)
+size_t pcmbuf_get_bufsize(void)
 {
     return pcmbuf_size;
 }
@@ -311,7 +379,7 @@
 /** Initialize a track switch so that audio playback will not stop but
  *  the switch to next track would happen as soon as possible.
  */
-void pcmbuf_flush_audio(void)
+static void pcmbuf_flush_audio(void)
 {
     if (crossfade_init || crossfade_active || !pcm_is_playing()) {
         pcmbuf_play_stop();
@@ -323,66 +391,71 @@
     crossfade_init = true;
 }
 
+void pcmbuf_pause(bool pause) {
+    pcm_mute(pause);
+    pcm_play_pause(!pause);
+    pcmbuf_boost(!pause);
+}
+
 /* Force playback. */
 void pcmbuf_play_start(void)
 {
+    mutex_lock(&pcmbuf_mutex);
+
     if (!pcm_is_playing() && pcmbuf_unplayed_bytes)
     {
         /** Prevent a very tiny pop from happening by muting audio
          *  until dma has been initialized. */
         pcm_mute(true);
     
-        pcm_play_data(pcmbuf_callback);
+        last_chunksize = pcmbuf_read->size;
+        pcmbuf_unplayed_bytes -= last_chunksize;
+        pcm_play_data(pcmbuf_callback, 
+                (unsigned char *)pcmbuf_read->addr, last_chunksize);
 
         /* Now unmute the audio. */
         pcm_mute(false);
     }
+
+    mutex_unlock(&pcmbuf_mutex);
 }
 
 /**
  * Commit samples waiting to the pcm buffer.
  */
-void pcmbuf_flush_fillpos(void)
+static void pcmbuf_flush_fillpos(void)
 {
-    int copy_n;
-
     mutex_lock(&pcmbuf_mutex);
-    
-    copy_n = MIN(audiobuffer_fillpos, CHUNK_SIZE);
-    
-    if (copy_n) {
-        while (!pcmbuf_add_chunk(&audiobuffer[audiobuffer_pos],
-                                   copy_n, pcmbuf_event_handler)) {
+
+    if (audiobuffer_fillpos) {
+        /* Never use the last buffer descriptor */
+        while (pcmbuf_write == pcmbuf_write_end) {
+            logf("pcmbuf_flush_fillpos no descriptors");
+            /* Deboost to let the playback catchup */
             pcmbuf_boost(false);
+            /* Let someone else have fun in the meantime */
             sleep(1);
             /* This is a fatal error situation that should never happen. */
             if (!pcm_is_playing()) {
-                logf("pcm_flush_fillpos error");
+                logf("pcmbuf_flush_fillpos error");
                 pcmbuf_play_start();
-                mutex_unlock(&pcmbuf_mutex);
                 return ;
             }
         }
-        position_callback = NULL;
-        pcmbuf_event_handler = NULL;
-        audiobuffer_pos += copy_n;
-        if (audiobuffer_pos >= pcmbuf_size)
-            audiobuffer_pos -= pcmbuf_size;
-        audiobuffer_free -= copy_n;
-        audiobuffer_fillpos -= copy_n;
+        pcmbuf_add_chunk();
     }
-    
+
     mutex_unlock(&pcmbuf_mutex);
 }
 
 /**
  * Completely process the crossfade fade out effect with current pcm buffer.
  */
-static void crossfade_process_buffer(
-        int fade_in_delay, int fade_out_delay, int fade_out_rem)
+static void crossfade_process_buffer(unsigned int fade_in_delay,
+        unsigned int fade_out_delay, size_t fade_out_rem)
 {
-    int amount;
-    int pos;
+    size_t amount;
+    size_t pos;
     short *buf;
 
     /* Fade out the entire current buffer according to settings. */
@@ -391,12 +464,12 @@
     
     while (fade_out_rem > 0 && crossfade_mode == CFM_CROSSFADE)
     {
-        int blocksize = MIN(8192, fade_out_rem);
+        size_t blocksize = MIN(8192, fade_out_rem);
         int factor = (fade_out_rem<<8)/amount;
 
         /* Prevent pcmbuffer from wrapping. */
-        if (pos >= pcmbuf_size)
-            pos -= pcmbuf_size;
+        if (pos >= pcmbuf_size) pos -= pcmbuf_size;
+
         blocksize = MIN((pcmbuf_size - pos)/2, blocksize);
         buf = (short *)&audiobuffer[pos];
         
@@ -408,7 +481,6 @@
             *buf++;
             blocksize--;
         }
-        //yield();
     }
 
     /* And finally set the mixing position where we should start fading in. */
@@ -425,12 +497,13 @@
  */
 static void crossfade_start(void)
 {
-    int bytesleft = pcmbuf_unplayed_bytes;
-    int fade_out_rem = 0, fade_out_delay = 0;
-    int fade_in_delay = 0;
+    size_t fade_out_rem = 0;
+    unsigned int fade_out_delay = 0;
+    unsigned fade_in_delay = 0;
     
     crossfade_init = 0;
-    if (bytesleft < CHUNK_SIZE * 4) {
+    /* Reject crossfade if less than .5s of data */
+    if (LOW_DATA(2)) {
         logf("crossfade rejected");
         pcmbuf_play_stop();
         return ;
@@ -438,18 +511,16 @@
 
     logf("crossfade_start");
     pcmbuf_boost(true);
-    while (audiobuffer_fillpos != 0)
-        pcmbuf_flush_fillpos();
+    pcmbuf_flush_fillpos();
     crossfade_active = true;
     crossfade_pos = audiobuffer_pos;
+    /* Initialize the crossfade buffer size to all of the buffered data that
+     * has not yet been sent to the DMA */
+    crossfade_rem = pcmbuf_unplayed_bytes / 2;
 
     switch (crossfade_mode) {
         case CFM_MIX:
         case CFM_CROSSFADE:
-            /* Initialize the crossfade buffer size. */
-            // FIXME: Crashes unless we use CHUNK_SIZE here
-            crossfade_rem = (bytesleft - (CHUNK_SIZE * 2))/2;
-
             /* Get fade out delay from settings. */
             fade_out_delay = NATIVE_FREQUENCY
                     * global_settings.crossfade_fade_out_delay * 2;
@@ -479,23 +550,20 @@
                     * global_settings.crossfade_fade_in_delay * 2;
         
             /* Decrease the fade out delay if necessary. */
-            fade_out_delay += MIN(crossfade_rem -
-                                  fade_out_rem -
-                                  fade_out_delay, 0);
-            if (fade_out_delay < 0)
-                fade_out_delay = 0;
+            if (crossfade_rem < fade_out_rem + fade_out_delay)
+                fade_out_delay -=
+                    (fade_out_rem + fade_out_delay) - crossfade_rem;
             break ;
 
         case CFM_FLUSH:
-            crossfade_rem = (bytesleft - CHUNK_SIZE) /2;
             crossfade_fade_in_rem = 0;
             crossfade_fade_in_amount = 0;
             break ;
     }
     
-    crossfade_pos -= crossfade_rem*2;
-    if (crossfade_pos < 0)
+    if (crossfade_pos < crossfade_rem * 2)
         crossfade_pos += pcmbuf_size;
+    crossfade_pos -= crossfade_rem*2;
     
     if (crossfade_mode != CFM_FLUSH) {
         /* Process the fade out part of the crossfade. */
@@ -508,29 +576,35 @@
  * Fades in samples passed to the function and inserts them
  * to the pcm buffer.
  */
-static void fade_insert(const short *inbuf, int length)
+static void fade_insert(const short *inbuf, size_t length)
 {
-    int copy_n;
+    size_t copy_n;
     int factor;
-    int i, samples;
+    unsigned int i, samples;
     short *buf;
     
-    factor = ((crossfade_fade_in_amount-crossfade_fade_in_rem)
-            <<8)/crossfade_fade_in_amount;
+    factor = ((crossfade_fade_in_amount-crossfade_fade_in_rem)<<8)
+        /crossfade_fade_in_amount;
 
-    while (audiobuffer_free < length + audiobuffer_fillpos
-        + CHUNK_SIZE)
+    while (audiobuffer_free < length)
     {
         pcmbuf_boost(false);
         sleep(1);
     }
+    audiobuffer_free -= length;
     
     while (length > 0) {
-        copy_n = MIN(length, pcmbuf_size - audiobuffer_pos -
-                audiobuffer_fillpos);
-        copy_n = MIN(CHUNK_SIZE - audiobuffer_fillpos, copy_n);
+        unsigned int audiobuffer_index = audiobuffer_pos + audiobuffer_fillpos;
+        /* Flush as needed */
+        if (NEED_FLUSH(audiobuffer_index))
+        {
+            pcmbuf_flush_fillpos();
+            audiobuffer_index = audiobuffer_pos + audiobuffer_fillpos;
+        }
+        
+        copy_n = MIN(length, pcmbuf_size - audiobuffer_index);
 
-        buf = (short *)&audiobuffer[audiobuffer_pos+audiobuffer_fillpos];
+        buf = (short *)&audiobuffer[audiobuffer_index];
         samples = copy_n / 2;
         for (i = 0; i < samples; i++)
             buf[i] = (inbuf[i] * factor) >> 8;
@@ -538,36 +612,30 @@
         inbuf += samples;
         audiobuffer_fillpos += copy_n;
         length -= copy_n;
-        
-        /* Pre-buffer to meet CHUNK_SIZE requirement */
-        if (audiobuffer_fillpos < CHUNK_SIZE && length == 0) {
-            break ;
-        }
-
-        pcmbuf_flush_fillpos();
     }
 }
 
 /**
  * Fades in buf2 and mixes it with buf.
  */
-static __inline
-int crossfade(short *buf, const short *buf2, int length)
+static int crossfade(short *buf, const short *buf2, unsigned int length)
 {
-    int size, i;
-    int size_insert = 0;
+    size_t size;
+    unsigned int i;
+    size_t size_insert = 0;
     int factor;
     
-    size = MAX(0, MIN(length, crossfade_rem));
+    size = MIN(length, crossfade_rem);
     switch (crossfade_mode) {
         /* Fade in the current stream and mix it. */
         case CFM_MIX:
         case CFM_CROSSFADE:
-            factor = ((crossfade_fade_in_amount-crossfade_fade_in_rem)
-                    <<8)/crossfade_fade_in_amount;
+            factor = ((crossfade_fade_in_amount-crossfade_fade_in_rem)<<8) /
+                crossfade_fade_in_amount;
             
             for (i = 0; i < size; i++) {
-                buf[i] = MIN(MAX(buf[i] + ((buf2[i] * factor) >> 8), -32768), 32767);
+                buf[i] = MIN(32767, MAX(-32768, 
+                            buf[i] + ((buf2[i] * factor) >> 8)));
             }
             break ;
 
@@ -580,25 +648,65 @@
             break ;
     }
 
-    crossfade_fade_in_rem = MAX(0, crossfade_fade_in_rem - size);
+    if (crossfade_fade_in_rem > size)
+        crossfade_fade_in_rem = crossfade_fade_in_rem - size;
+    else
+        crossfade_fade_in_rem = 0;
+
     crossfade_rem -= size;
-    if (crossfade_rem <= 0)
+    if (crossfade_rem == 0)
     {
         if (crossfade_fade_in_rem > 0 && crossfade_fade_in_amount > 0)
         {
-            size_insert = MAX(0, MIN(crossfade_fade_in_rem, length - size));
+            size_insert = MIN(crossfade_fade_in_rem, length - size);
             fade_insert(&buf2[size], size_insert*2);
             crossfade_fade_in_rem -= size_insert;
         }
         
-        if (crossfade_fade_in_rem <= 0)
+        if (crossfade_fade_in_rem == 0)
             crossfade_active = false;
     }
 
     return size + size_insert;
 }
 
-static bool prepare_insert(long length)
+static void pcmbuf_flush_buffer(const char *buf, size_t length)
+{
+    size_t copy_n;
+    audiobuffer_free -= length;
+    while (length > 0) {
+        size_t audiobuffer_index = audiobuffer_pos + audiobuffer_fillpos;
+        if (NEED_FLUSH(audiobuffer_index))
+        {
+            pcmbuf_flush_fillpos();
+            audiobuffer_index = audiobuffer_pos + audiobuffer_fillpos;
+        }
+        copy_n = MIN(length, pcmbuf_size - audiobuffer_index);
+        memcpy(&audiobuffer[audiobuffer_index], buf, copy_n);
+        buf += copy_n;
+        audiobuffer_fillpos += copy_n;
+        length -= copy_n;
+    }
+}
+
+static void flush_crossfade(const char *buf, size_t length) {
+    size_t copy_n;
+
+    while (length > 0 && crossfade_active) {
+        copy_n = MIN(length, pcmbuf_size - crossfade_pos);
+        copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos],
+                (const short *)buf, copy_n/2);
+        buf += copy_n;
+        length -= copy_n;
+        crossfade_pos += copy_n;
+        if (crossfade_pos >= pcmbuf_size)
+            crossfade_pos = 0;
+    }
+
+    pcmbuf_flush_buffer(buf, length);
+}
+
+static bool prepare_insert(size_t length)
 {
     if (crossfade_init)
         crossfade_start();
@@ -611,48 +719,66 @@
             return false;
     }
     
-    if (audiobuffer_free < length + audiobuffer_fillpos
-           + CHUNK_SIZE && !crossfade_active) {
+    if (audiobuffer_free < length && !crossfade_active)
+    {
         pcmbuf_boost(false);
         return false;
     }
 
-    if (!pcm_is_playing()) {
+    if (!pcm_is_playing())
+    {
         pcmbuf_boost(true);
         crossfade_active = false;
         /* Pre-buffer 1s. */
-        if (audiobuffer_free < pcmbuf_size - NATIVE_FREQUENCY*4) {
+        if (!LOW_DATA(4))
+        {
             logf("pcm starting");
             pcmbuf_play_start();
         }
     }
-    
     return true;
 }
 
-void* pcmbuf_request_buffer(long length, long *realsize)
+void* pcmbuf_request_buffer(size_t length, size_t *realsize)
 {
-    void *ptr = NULL;
-
-    if (!prepare_insert(length))
-    {
-        *realsize = 0;
-        return NULL;
-    }
-    
     if (crossfade_active) {
-        *realsize = MIN(length, PCMBUF_GUARD);
-        ptr = &guardbuf[0];
-    } else {
-        *realsize = MIN(length, pcmbuf_size - audiobuffer_pos
-                            - audiobuffer_fillpos);
-        if (*realsize < length) {
-            *realsize += MIN((long)(length - *realsize), PCMBUF_GUARD);
-        }
-        ptr = &audiobuffer[audiobuffer_pos + audiobuffer_fillpos];
+        *realsize = MIN(length, PCMBUF_FADE_CHUNK);
+        return &guardbuf[0];
     }
-    
-    return ptr;
+    else
+    {
+        if(prepare_insert(length))
+        {
+            size_t audiobuffer_index = audiobuffer_pos + audiobuffer_fillpos;
+            if (pcmbuf_size - audiobuffer_index < PCMBUF_MIN_CHUNK) {
+                pcmbuf_flush_fillpos();
+                audiobuffer_pos = 0;
+                *realsize = MIN(length, pcmbuf_size);
+                return &audiobuffer[0];
+            }
+            else
+            {
+                *realsize = MIN(length, pcmbuf_size - audiobuffer_index);
+                return &audiobuffer[audiobuffer_index];
+            }
+        }
+        else
+        {
+            *realsize = 0;
+            return NULL;
+        }
+    }
+}
+
+void* pcmbuf_request_voice_buffer(size_t length, size_t *realsize, bool mix)
+{
+    if (mix)
+    {
+        *realsize = MIN(length, PCMBUF_FADE_CHUNK);
+        return &guardbuf[0];
+    }
+    else
+        return pcmbuf_request_buffer(length, realsize);
 }
 
 bool pcmbuf_is_crossfade_active(void)
@@ -660,104 +786,41 @@
     return crossfade_active || crossfade_init;
 }
 
-void pcmbuf_flush_buffer(long length)
+void pcmbuf_write_complete(size_t length)
 {
-    int copy_n;
-    char *buf;
-
     if (crossfade_active) {
-        buf = &guardbuf[0];
-        length = MIN(length, PCMBUF_GUARD);
-        while (length > 0 && crossfade_active) {
-            copy_n = MIN(length, pcmbuf_size - crossfade_pos);
-            copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos], 
-                    (const short *)buf, copy_n/2);
-            buf += copy_n;
-            length -= copy_n;
-            crossfade_pos += copy_n;
-            if (crossfade_pos >= pcmbuf_size)
-                crossfade_pos -= pcmbuf_size;
-        }
-        
-        while (length > 0) {
+        length = MIN(length, PCMBUF_FADE_CHUNK);
+        flush_crossfade(&guardbuf[0],length);
+    }
+    else
+    {
+        audiobuffer_free -= length;
+        audiobuffer_fillpos += length;
+
+        if (NEED_FLUSH(audiobuffer_pos + audiobuffer_fillpos))
             pcmbuf_flush_fillpos();
-            copy_n = MIN(length, pcmbuf_size - audiobuffer_pos);
-            memcpy(&audiobuffer[audiobuffer_pos], buf, copy_n);
-            audiobuffer_fillpos = copy_n;
-            buf += copy_n;
-            length -= copy_n;
-        }
     }
-
-    audiobuffer_fillpos += length;
-
-    try_flush:
-    if (audiobuffer_fillpos < CHUNK_SIZE && pcmbuf_size
-        - audiobuffer_pos - audiobuffer_fillpos > 0)
-        return ;
-
-    copy_n = audiobuffer_fillpos - (pcmbuf_size - audiobuffer_pos);
-    if (copy_n > 0) {
-        audiobuffer_fillpos -= copy_n;
-        pcmbuf_flush_fillpos();
-        copy_n = MIN(copy_n, PCMBUF_GUARD);
-        memcpy(&audiobuffer[0], &guardbuf[0], copy_n);
-        audiobuffer_fillpos = copy_n;
-        goto try_flush;
-    }
-    pcmbuf_flush_fillpos();
 }
 
-bool pcmbuf_insert_buffer(char *buf, long length)
+void pcmbuf_write_voice(size_t length)
 {
-    long copy_n = 0;
-    
+    while (pcm_is_playing())
+        sleep(1);
+    pcm_play_data(NULL, &guardbuf[0], length);
+}
+
+bool pcmbuf_insert_buffer(const char *buf, size_t length)
+{
     if (!prepare_insert(length))
         return false;
 
-    
     if (crossfade_active) {
-        while (length > 0 && crossfade_active) {
-            copy_n = MIN(length, pcmbuf_size - crossfade_pos);
-                
-            copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos],
-                                    (const short *)buf, copy_n/2);
-            buf += copy_n;
-            length -= copy_n;
-            crossfade_pos += copy_n;
-            if (crossfade_pos >= pcmbuf_size)
-                crossfade_pos -= pcmbuf_size;
-        }
-        
-        while (length > 0) {
-            pcmbuf_flush_fillpos();
-            copy_n = MIN(length, pcmbuf_size - audiobuffer_pos);
-            memcpy(&audiobuffer[audiobuffer_pos], buf, copy_n);
-            audiobuffer_fillpos = copy_n;
-            buf += copy_n;
-            length -= copy_n;
-        }
+        flush_crossfade(buf,length);
     }
-        
-    while (length > 0) {
-        copy_n = MIN(length, pcmbuf_size - audiobuffer_pos -
-                    audiobuffer_fillpos);
-        copy_n = MIN(CHUNK_SIZE - audiobuffer_fillpos, copy_n);
-
-        memcpy(&audiobuffer[audiobuffer_pos+audiobuffer_fillpos],
-                buf, copy_n);
-        buf += copy_n;
-        audiobuffer_fillpos += copy_n;
-        length -= copy_n;
-        
-        /* Pre-buffer to meet CHUNK_SIZE requirement */
-        if (audiobuffer_fillpos < CHUNK_SIZE && length == 0) {
-            return true;
-        }
-
-        pcmbuf_flush_fillpos();
+    else
+    {
+        pcmbuf_flush_buffer(buf, length);
     }
-
     return true;
 }
 
@@ -765,19 +828,21 @@
    in Hertz for a duration in milliseconds. */
 void pcmbuf_beep(int frequency, int duration, int amplitude)
 {
-    int state = 0, count = 0;
-    int interval = NATIVE_FREQUENCY / frequency;
-    int pos;
+    unsigned int state = 0, count = 0;
+    unsigned int interval = NATIVE_FREQUENCY / frequency;
+    size_t pos;
     short *buf = (short *)audiobuffer;
-    int bufsize = pcmbuf_size / 2;
+    size_t bufsize = pcmbuf_size / 2;
     
     /* FIXME: Should start playback. */
     //if (pcmbuf_unplayed_bytes * 1000 < 4 * NATIVE_FREQUENCY * duration)
     //    return ;
 
-    pos = (audiobuffer_pos - pcmbuf_unplayed_bytes) / 2;
-    if (pos < 0)
-        pos += bufsize;
+    if (audiobuffer_pos < pcmbuf_unplayed_bytes)
+        pos = pcmbuf_size + audiobuffer_pos - pcmbuf_unplayed_bytes;
+    else
+        pos = audiobuffer_pos - pcmbuf_unplayed_bytes;
+    pos /= 2;
 
     duration = NATIVE_FREQUENCY / 1000 * duration;
     while (duration-- > 0)
@@ -821,21 +886,19 @@
 
 void pcmbuf_reset_mixpos(void)
 {
-    int bufsize = pcmbuf_size / 2;
-
     pcmbuf_mix_used_bytes = 0;
-    mixpos = (audiobuffer_pos - pcmbuf_unplayed_bytes) / 2;
-    if (mixpos < 0)
-        mixpos += bufsize;
-    if (mixpos >= bufsize)
-        mixpos -= bufsize;
+    if (audiobuffer_pos < pcmbuf_unplayed_bytes)
+        mixpos = pcmbuf_size + audiobuffer_pos - pcmbuf_unplayed_bytes;
+    else
+        mixpos = audiobuffer_pos - pcmbuf_unplayed_bytes;
+    mixpos /= 2;
 }
 
-void pcmbuf_mix(char *buf, long length)
+void pcmbuf_mix(char *buf, size_t length)
 {
     short *ibuf = (short *)buf;
     short *obuf = (short *)audiobuffer;
-    int bufsize = pcmbuf_size / 2;
+    size_t bufsize = pcmbuf_size / 2;
 
     if (pcmbuf_mix_used_bytes == 0)
         pcmbuf_reset_mixpos();
@@ -858,8 +921,10 @@
     crossfade_enabled = on_off;
 
     if (crossfade_enabled) {
-        pcmbuf_set_watermark_bytes(pcmbuf_size - (NATIVE_FREQUENCY*4/2));
+        /* If crossfading, try to keep the buffer full other than 2 second */
+        pcmbuf_set_watermark_bytes(pcmbuf_size - PCMBUF_WATERMARK * 2);
     } else {
+        /* Otherwise, just keep it above 1 second */
         pcmbuf_set_watermark_bytes(PCMBUF_WATERMARK);
     }
 }
diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h
index 2476857..555c1bc 100644
--- a/apps/pcmbuf.h
+++ b/apps/pcmbuf.h
@@ -19,36 +19,48 @@
 #ifndef PCMBUF_H
 #define PCMBUF_H
 
-/* Guard buffer for crossfader when dsp is enabled. */
-#define PCMBUF_GUARD  32768
+#define PCMBUF_TARGET_CHUNK 32768 /* This is the target fill size of chunks
+                                     on the pcm buffer */
+#define PCMBUF_MINAVG_CHUNK 24576 /* This is the minimum average size of
+                                     chunks on the pcm buffer (or we run out
+                                     of buffer descriptors, which is
+                                     non-fatal) */
+#define PCMBUF_MIN_CHUNK     4096 /* We try to never feed a chunk smaller than
+                                     this to the DMA */
+#define PCMBUF_FADE_CHUNK    8192 /* This is the maximum size of one packet
+                                     for mixing (crossfade or voice) */
 
-void pcmbuf_init(long bufsize);
-long pcmbuf_get_bufsize(void);
+/* Returns true if the buffer needs to change size */
+bool pcmbuf_is_same_size(size_t bufsize);
+void pcmbuf_init(size_t bufsize);
+/* Size in bytes used by the pcmbuffer */
+size_t pcmbuf_get_bufsize(void);
+size_t get_pcmbuf_descsize(void);
 
+void pcmbuf_pause(bool pause);
 void pcmbuf_play_stop(void);
 bool pcmbuf_is_crossfade_active(void);
 
 /* These functions are for playing chained buffers of PCM data */
-bool pcmbuf_add_chunk(void *addr, int size, void (*callback)(void));
-
-#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+#if defined(HAVE_ADJUSTABLE_CPU_FREQ) && !defined(SIMULATOR)
 void pcmbuf_boost(bool state);
 void pcmbuf_set_boost_mode(bool state);
 #else
-#define pcmbuf_boost(state)          do { } while(0)
+#define pcmbuf_boost(state) do { } while(0)
 #define pcmbuf_set_boost_mode(state) do { } while(0)
 #endif
 bool pcmbuf_is_lowdata(void);
-void pcmbuf_flush_audio(void);
 void pcmbuf_play_start(void);
 bool pcmbuf_crossfade_init(bool manual_skip);
-void pcmbuf_add_event(void (*event_handler)(void));
-void pcmbuf_set_position_callback(void (*callback)(int size));
+void pcmbuf_set_event_handler(void (*callback)(void));
+void pcmbuf_set_position_callback(void (*callback)(size_t size));
 unsigned int pcmbuf_get_latency(void);
 void pcmbuf_set_low_latency(bool state);
-bool pcmbuf_insert_buffer(char *buf, long length);
-void pcmbuf_flush_buffer(long length);
-void* pcmbuf_request_buffer(long length, long *realsize);
+bool pcmbuf_insert_buffer(const char *buf, size_t length);
+void pcmbuf_write_complete(size_t length);
+void pcmbuf_write_voice(size_t length);
+void* pcmbuf_request_buffer(size_t length, size_t *realsize);
+void* pcmbuf_request_voice_buffer(size_t length, size_t *realsize, bool mix);
 bool pcmbuf_is_crossfade_enabled(void);
 void pcmbuf_crossfade_enable(bool on_off);
 
@@ -56,6 +68,9 @@
 int pcmbuf_mix_usage(void);
 void pcmbuf_beep(int frequency, int duration, int amplitude);
 void pcmbuf_reset_mixpos(void);
-void pcmbuf_mix(char *buf, long length);
+void pcmbuf_mix(char *buf, size_t length);
+
+int pcmbuf_used_descs(void);
+int pcmbuf_descs(void);
 
 #endif
diff --git a/apps/playback.c b/apps/playback.c
index 5ed6c5e..7688534 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -216,6 +216,13 @@
 static void mp3_set_elapsed(struct mp3entry* id3);
 int mp3_get_file_pos(void);
 
+#ifdef TIME_CODEC
+bool is_filling(void)
+{
+    return filling;
+}
+#endif
+
 static void do_swap(int idx_old, int idx_new)
 {
 #ifndef SIMULATOR
@@ -287,13 +294,13 @@
 #define voice_boost_cpu(state)   do { } while(0)
 #endif
 
-bool codec_pcmbuf_insert_split_callback(void *ch1, void *ch2,
-                                        long length)
+bool codec_pcmbuf_insert_split_callback(const void *ch1, const void *ch2,
+                                        size_t length)
 {
-    char* src[2];
+    const char* src[2];
     char *dest;
     long input_size;
-    long output_size;
+    size_t output_size;
 
     src[0] = ch1;
     src[1] = ch2;
@@ -311,47 +318,50 @@
     }
 
     while (length > 0) {
+        long est_output_size = dsp_output_size(length);
         /* This will prevent old audio from playing when skipping tracks. */
-        if ((ci.reload_codec || ci.stop_codec) && 
-                current_codec != CODEC_IDX_VOICE)
-            return true;
-    
-        while ((dest = pcmbuf_request_buffer(dsp_output_size(length), 
-            &output_size)) == NULL) {
-            sleep(1);
-            if ((ci.reload_codec || ci.stop_codec) && 
-                    current_codec != CODEC_IDX_VOICE)
+        if (current_codec == CODEC_IDX_VOICE) {
+            while ((dest = pcmbuf_request_voice_buffer(est_output_size,
+                            &output_size, audio_codec_loaded)) == NULL)
+                sleep(1);
+        }
+        else
+        {
+            if (ci.reload_codec || ci.stop_codec) 
                 return true;
+
+            while ((dest = pcmbuf_request_buffer(est_output_size,
+                            &output_size)) == NULL) {
+                sleep(1);
+                if (ci.reload_codec || ci.stop_codec)
+                    return true;
+            }
         }
 
         /* Get the real input_size for output_size bytes, guarding
          * against resampling buffer overflows. */
         input_size = dsp_input_size(output_size);
-        if (input_size > length) {
+        
+        if (input_size <= 0) {
+            DEBUGF("Warning: dsp_input_size(%ld=dsp_output_size(%ld))=%ld <= 0\n",
+                   output_size, length, input_size);
+            /* this cannot happen */
+            break;
+        }
+
+        if ((size_t)input_size > length) {
             DEBUGF("Error: dsp_input_size(%ld=dsp_output_size(%ld))=%ld > %ld\n",
                    output_size, length, input_size, length);
             input_size = length;
         }
         
-        if (input_size <= 0) {
-            pcmbuf_flush_buffer(0);
-            DEBUGF("Warning: dsp_input_size(%ld=dsp_output_size(%ld))=%ld <= 0\n",
-                   output_size, length, input_size);
-            /* should we really continue, or should we break?
-             * We should probably continue because calling 
-             * pcmbuf_flush_buffer(0) will wrap the buffer if it was fully 
-             * filled and so next call to pcmbuf_request_buffer should give 
-             * the requested output_size. */
-            continue;
-        }
-
         output_size = dsp_process(dest, src, input_size);
 
         /* Hotswap between audio and voice codecs as necessary. */
         switch (current_codec)
         {
             case CODEC_IDX_AUDIO:
-                pcmbuf_flush_buffer(output_size);
+                pcmbuf_write_complete(output_size);
                 if (voice_is_playing && pcmbuf_usage() > 30
                     && pcmbuf_mix_usage() < 20)
                 {
@@ -368,7 +378,7 @@
                         || pcmbuf_mix_usage() > 70)
                         swap_codec();
                 } else {
-                    pcmbuf_flush_buffer(output_size);
+                    pcmbuf_write_complete(output_size);
                 }
                 break ;
         }
@@ -379,7 +389,7 @@
     return true;
 }
 
-bool codec_pcmbuf_insert_callback(char *buf, long length)
+bool codec_pcmbuf_insert_callback(const char *buf, size_t length)
 {
     /* TODO: The audiobuffer API should probably be updated, and be based on
      *       pcmbuf_insert_split().
@@ -405,9 +415,10 @@
     return &audiobuf[0];
 }
 
-static void pcmbuf_position_callback(int size) ICODE_ATTR;
-static void pcmbuf_position_callback(int size) {
-    unsigned int time = size * 1000 / 4 / 44100 + prev_ti->id3.elapsed;
+static void pcmbuf_position_callback(size_t size) ICODE_ATTR;
+static void pcmbuf_position_callback(size_t size) {
+    unsigned int time = size * 1000 / 4 / NATIVE_FREQUENCY +
+        prev_ti->id3.elapsed;
     if (time >= prev_ti->id3.length) {
         pcmbuf_set_position_callback(NULL);
         prev_ti->id3.elapsed = prev_ti->id3.length;
@@ -785,6 +796,13 @@
     queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0);
 }
 
+static void pcmbuf_track_changed_callback(void)
+{
+    track_changed = true;
+    pcmbuf_set_position_callback(NULL);
+    queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0);
+}
+
 /* Give codecs or file buffering the right amount of processing time
    to prevent pcm audio buffer from going empty. */
 static void yield_codecs(void)
@@ -1529,7 +1547,7 @@
     /* Gapless playback. */
     else
     {
-        pcmbuf_add_event(codec_track_changed);
+        pcmbuf_set_event_handler(pcmbuf_track_changed_callback);
     }
 }
 
@@ -1675,15 +1693,17 @@
            the core has been requested the codec to be terminated. */
         return !ci_voice.stop_codec && queue_empty(&voice_codec_queue);
     }
-#ifdef AB_REPEAT_ENABLE
-    ab_end_of_track_report();
-#endif
-
-    pcmbuf_set_position_callback(pcmbuf_position_callback);
 
     if (ci.stop_codec || !playing)
         return false;
     
+#ifdef AB_REPEAT_ENABLE
+    ab_end_of_track_report();
+#endif
+    
+    if (!new_track)
+        pcmbuf_set_position_callback(pcmbuf_position_callback);
+
     logf("Request new track");
 
     /* Advance to next track. */
@@ -1856,15 +1876,13 @@
                 
             case Q_AUDIO_PAUSE:
                 logf("audio_pause");
-                pcm_mute(true);
-                pcm_play_pause(false);
+                pcmbuf_pause(true);
                 paused = true;
                 break ;
                 
             case Q_AUDIO_RESUME:
                 logf("audio_resume");
-                pcm_play_pause(true);
-                pcm_mute(false);
+                pcmbuf_pause(false);
                 paused = false;
                 break ;
             
@@ -2022,8 +2040,9 @@
 static void reset_buffer(void)
 {
     filebuf = (char *)&audiobuf[MALLOC_BUFSIZE];
-    filebuflen = audiobufend - audiobuf - pcmbuf_get_bufsize()
-                  - PCMBUF_GUARD - MALLOC_BUFSIZE - GUARD_BUFSIZE;
+    filebuflen = audiobufend - audiobuf - MALLOC_BUFSIZE - GUARD_BUFSIZE -
+        (pcmbuf_get_bufsize() + get_pcmbuf_descsize() + PCMBUF_FADE_CHUNK);
+        
                   
     if (talk_get_bufsize() && voice_codec_loaded)
     {
@@ -2422,7 +2441,7 @@
 /* Set crossfade & PCM buffer length. */
 void audio_set_crossfade(int enable)
 {
-    long size;
+    size_t size;
     bool was_playing = playing;
     int offset = 0;
     int seconds = 1;
diff --git a/apps/plugin.c b/apps/plugin.c
index 286c36c..f5b33c6 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -124,6 +124,10 @@
     lcd_bitmap_part,
     lcd_bitmap,
 #endif
+#if LCD_DEPTH == 16
+    lcd_bitmap_transparent_part,
+    lcd_bitmap_transparent,
+#endif
     lcd_putsxy,
     lcd_puts_style,
     lcd_puts_scroll_style,
@@ -198,6 +202,7 @@
     settings_parseline,
 #ifndef SIMULATOR
     ata_sleep,
+	ata_disk_is_active,
 #endif
 
     /* dir */
@@ -225,6 +230,17 @@
     timer_unregister,
     timer_set_period,
 #endif
+    queue_init,
+    queue_delete,
+    queue_post,
+    queue_wait_w_tmo,
+    usb_acknowledge,
+#ifdef RB_PROFILE
+    profile_thread,
+    profstop,
+    profile_func_enter,
+    profile_func_exit,
+#endif
 
     /* strings and memory */
     snprintf,
@@ -238,6 +254,7 @@
     strncasecmp,
     memset,
     memcpy,
+    memmove,
     _ctype_,
     atoi,
     strchr,
@@ -332,6 +349,23 @@
     menu_insert,
     menu_set_cursor,
 
+    /* power */
+    battery_level,
+    battery_level_safe,
+    battery_time,
+#ifndef SIMULATOR
+	battery_voltage,
+#endif
+#ifdef HAVE_CHARGING
+    charger_inserted,
+# ifdef HAVE_CHARGE_STATE
+    charging_state,
+# endif
+#endif
+#ifdef HAVE_USB_POWER
+        usb_powered,
+#endif
+
     /* misc */
     srand,
     rand,
@@ -353,8 +387,6 @@
     count_mp3_frames,
     create_xing_header,
     find_next_frame,
-    battery_level,
-    battery_level_safe,
 #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
     peak_meter_scale_value,
     peak_meter_set_use_dbfs,
@@ -368,37 +400,7 @@
 
     /* new stuff at the end, sort into place next time
        the API gets incompatible */
-#ifdef RB_PROFILE
-    profile_thread,
-    profstop,
-    profile_func_enter,
-    profile_func_exit,
-#endif
-    battery_time,
-#ifndef SIMULATOR
-	ata_disk_is_active,
-	battery_voltage,
-#endif
-    queue_init,
-    queue_delete,
-    queue_post,
-    queue_wait_w_tmo,
-    usb_acknowledge,
-#if LCD_DEPTH == 16
-    lcd_bitmap_transparent_part,
-    lcd_bitmap_transparent,
-#endif
-    memmove,
-#ifdef HAVE_CHARGING
-    charger_inserted,
-# ifdef HAVE_CHARGE_STATE
-    charging_state,
-# endif
-#endif
-#ifdef HAVE_USB_POWER
-        usb_powered,
-#endif
-    
+
 };
 
 int plugin_load(const char* plugin, void* parameter)
diff --git a/apps/plugin.h b/apps/plugin.h
index 3d9161a..286ca40 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -97,12 +97,12 @@
 #define PLUGIN_MAGIC 0x526F634B /* RocK */
 
 /* increase this every time the api struct changes */
-#define PLUGIN_API_VERSION 6
+#define PLUGIN_API_VERSION 7
 
 /* update this to latest version if a change to the api struct breaks
    backwards compatibility (and please take the opportunity to sort in any 
    new function which are "waiting" at the end of the function table) */
-#define PLUGIN_MIN_API_VERSION 2
+#define PLUGIN_MIN_API_VERSION 7
 
 /* plugin return codes */
 enum plugin_status {
@@ -162,6 +162,13 @@
     void (*lcd_bitmap)(const fb_data *src, int x, int y, int width,
                        int height);
 #endif
+#if LCD_DEPTH == 16
+    void (*lcd_bitmap_transparent_part)(const fb_data *src,
+            int src_x, int src_y, int stride,
+            int x, int y, int width, int height);
+  	void (*lcd_bitmap_transparent)(const fb_data *src, int x, int y,
+            int width, int height);
+#endif
     void (*lcd_putsxy)(int x, int y, const unsigned char *string);
     void (*lcd_puts_style)(int x, int y, const unsigned char *str, int style);
     void (*lcd_puts_scroll_style)(int x, int y, const unsigned char* string,
@@ -246,6 +253,7 @@
     bool (*settings_parseline)(char* line, char** name, char** value);
 #ifndef SIMULATOR
     void (*ata_sleep)(void);
+    bool (*ata_disk_is_active)(void);
 #endif
     
     /* dir */
@@ -275,6 +283,18 @@
     void (*timer_unregister)(void);
     bool (*timer_set_period)(long count);
 #endif
+    void (*queue_init)(struct event_queue *q);
+    void (*queue_delete)(struct event_queue *q);
+    void (*queue_post)(struct event_queue *q, long id, void *data);
+    void (*queue_wait_w_tmo)(struct event_queue *q, struct event *ev,
+            int ticks);
+    void (*usb_acknowledge)(long id);
+#ifdef RB_PROFILE
+    void (*profile_thread)(void);
+    void (*profstop)(void);
+    void (*profile_func_enter)(void *this_fn, void *call_site);
+    void (*profile_func_exit)(void *this_fn, void *call_site);
+#endif
 
     /* strings and memory */
     int (*snprintf)(char *buf, size_t size, const char *fmt, ...);
@@ -288,6 +308,7 @@
     int (*strncasecmp)(const char *s1, const char *s2, size_t n);
     void* (*memset)(void *dst, int c, size_t length);
     void* (*memcpy)(void *out, const void *in, size_t n);
+    void* (*memmove)(void *out, const void *in, size_t n);
     const unsigned char *_ctype_;
     int (*atoi)(const char *str);
     char *(*strchr)(const char *s, int c);
@@ -315,7 +336,8 @@
     void (*bitswap)(unsigned char *data, int length);
 #endif
 #if CONFIG_CODEC == SWCODEC
-    void (*pcm_play_data)(void (*get_more)(unsigned char** start, long*size));
+    void (*pcm_play_data)(void (*get_more)(unsigned char** start, size_t*size),
+            unsigned char* start, size_t size);
     void (*pcm_play_stop)(void);
     void (*pcm_set_frequency)(unsigned int frequency);
     bool (*pcm_is_playing)(void);
@@ -384,6 +406,23 @@
     void (*menu_insert)(int menu, int position, char *desc, bool (*function) (void));
     void (*menu_set_cursor)(int menu, int position);
 
+    /* power */
+    int (*battery_level)(void);
+    bool (*battery_level_safe)(void);
+    int (*battery_time)(void);
+#ifndef SIMULATOR
+    unsigned int (*battery_voltage)(void);
+#endif
+#ifdef HAVE_CHARGING
+    bool (*charger_inserted)(void);
+# ifdef HAVE_CHARGE_STATE
+    bool (*charging_state)(void);
+# endif    
+#endif
+#ifdef HAVE_USB_POWER
+    bool (*usb_powered)(void);
+#endif
+
     /* misc */
     void (*srand)(unsigned int seed);
     int  (*rand)(void);
@@ -406,13 +445,12 @@
     int (*count_mp3_frames)(int fd, int startpos, int filesize,
                             void (*progressfunc)(int));
     int (*create_xing_header)(int fd, long startpos, long filesize,
-                              unsigned char *buf, unsigned long num_frames,
-                              unsigned long rec_time, unsigned long header_template,
-                              void (*progressfunc)(int), bool generate_toc);
+            unsigned char *buf, unsigned long num_frames,
+            unsigned long rec_time, unsigned long header_template,
+            void (*progressfunc)(int), bool generate_toc);
     unsigned long (*find_next_frame)(int fd, long *offset,
-                                     long max_offset, unsigned long last_header);
-    int (*battery_level)(void);
-    bool (*battery_level_safe)(void);
+            long max_offset, unsigned long last_header);
+
 #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
     unsigned short (*peak_meter_scale_value)(unsigned short val,
                                              int meterwidth);
@@ -429,40 +467,6 @@
     /* new stuff at the end, sort into place next time
        the API gets incompatible */     
        
-#ifdef RB_PROFILE
-    void (*profile_thread)(void);
-    void (*profstop)(void);
-    void (*profile_func_enter)(void *this_fn, void *call_site);
-    void (*profile_func_exit)(void *this_fn, void *call_site);
-#endif
-    int (*battery_time)(void);
-#ifndef SIMULATOR
-    bool (*ata_disk_is_active)(void);
-    unsigned int (*battery_voltage)(void);
-#endif
-    void (*queue_init)(struct event_queue *q);
-    void (*queue_delete)(struct event_queue *q);
-    void (*queue_post)(struct event_queue *q, long id, void *data);
-    void (*queue_wait_w_tmo)(struct event_queue *q, struct event *ev, int ticks);
-    void (*usb_acknowledge)(long id);
-#if LCD_DEPTH == 16
-    void (*lcd_bitmap_transparent_part)(const fb_data *src, int src_x, int src_y,
-  	                                     int stride, int x, int y, int width, int height);
-  	void (*lcd_bitmap_transparent)(const fb_data *src, int x, int y,
-  	                            int width, int height);
-#endif
-    void* (*memmove)(void *out, const void *in, size_t n);
-
-#ifdef HAVE_CHARGING
-    bool (*charger_inserted)(void);
-# ifdef HAVE_CHARGE_STATE
-    bool (*charging_state)(void);
-# endif    
-#endif
-#ifdef HAVE_USB_POWER
-    bool (*usb_powered)(void);
-#endif
-
 };
 
 /* plugin header */
diff --git a/apps/plugins/metronome.c b/apps/plugins/metronome.c
index 2f897d7..ac4a990 100644
--- a/apps/plugins/metronome.c
+++ b/apps/plugins/metronome.c
@@ -736,18 +736,8 @@
     }
 }
 
-void callback_pcm(unsigned char** start, long* size)
-{
-    if(sound_active) {
-        *start = (unsigned char *)sndbuf;
-        *size = sizeof(sndbuf);
-        sound_active = false;
-    }
-}
-
 void play_tock(void) {
-    sound_active = true;
-    rb->pcm_play_data(callback_pcm);
+    rb->pcm_play_data(NULL,(unsigned char *)sndbuf,sizeof(sndbuf));
     tock++;
 }
 
diff --git a/apps/plugins/rockboy/rbsound.c b/apps/plugins/rockboy/rbsound.c
index 3eebea8..6371212 100644
--- a/apps/plugins/rockboy/rbsound.c
+++ b/apps/plugins/rockboy/rbsound.c
@@ -35,7 +35,7 @@
 
 static bool newly_started;
 
-void get_more(unsigned char** start, long* size)
+void get_more(unsigned char** start, size_t* size)
 {
 #ifdef ONEBUF
    doneplay=1;
@@ -108,7 +108,7 @@
     
    if(newly_started)
    {  
-      rb->pcm_play_data(&get_more);
+      rb->pcm_play_data(&get_more,NULL,0);
       newly_started = false;
    }    
    
diff --git a/firmware/export/pcm_playback.h b/firmware/export/pcm_playback.h
index 5b61beb..a4cd939 100644
--- a/firmware/export/pcm_playback.h
+++ b/firmware/export/pcm_playback.h
@@ -23,10 +23,11 @@
 void pcm_set_frequency(unsigned int frequency);
 
 /* This is for playing "raw" PCM data */
-void pcm_play_data(void (*get_more)(unsigned char** start, long* size));
+void pcm_play_data(void (*get_more)(unsigned char** start, size_t* size),
+        unsigned char* start, size_t size);
 
 void pcm_calculate_peaks(int *left, int *right);
-long pcm_get_bytes_waiting(void);
+size_t pcm_get_bytes_waiting(void);
 
 void pcm_play_stop(void);
 void pcm_mute(bool mute);
diff --git a/firmware/pcm_playback.c b/firmware/pcm_playback.c
index 0d9af14..d3e9f3e 100644
--- a/firmware/pcm_playback.c
+++ b/firmware/pcm_playback.c
@@ -61,11 +61,11 @@
 static bool pcm_paused;
 static int pcm_freq = 0x6; /* 44.1 is default */
 
-static unsigned char *next_start IDATA_ATTR;
-static long next_size IDATA_ATTR;
+size_t next_size IBSS_ATTR;
+unsigned char *next_start IBSS_ATTR;
 
 /* Set up the DMA transfer that kicks in when the audio FIFO gets empty */
-static void dma_start(const void *addr, long size)
+static void dma_start(const void *addr, size_t size)
 {
     pcm_playing = true;
 
@@ -104,8 +104,6 @@
     EBU1CONFIG = IIS_RESET | EBU_DEFPARM;
 #endif
 
-    next_start = NULL;
-    next_size = 0;
     pcm_paused = false;
 }
 
@@ -131,23 +129,27 @@
 }
 
 /* the registered callback function to ask for more mp3 data */
-static void (*callback_for_more)(unsigned char**, long*) IDATA_ATTR = NULL;
+static void (*callback_for_more)(unsigned char**, size_t*) IDATA_ATTR = NULL;
 
-void pcm_play_data(void (*get_more)(unsigned char** start, long* size))
+void pcm_play_data(void (*get_more)(unsigned char** start, size_t* size),
+        unsigned char* start, size_t size)
 {
-    unsigned char *start;
-    long size;
-
     callback_for_more = get_more;
 
-    get_more((unsigned char **)&start, (long *)&size);
-    get_more(&next_start, &next_size);
-    dma_start(start, size);
+    if (!(start && size))
+    {
+        if (get_more)
+            get_more(&start, &size);
+        else
+            return;
+    }
+    if (start && size)
+        dma_start(start, size);
 }
 
-long pcm_get_bytes_waiting(void)
+size_t pcm_get_bytes_waiting(void)
 {
-    return next_size + (BCR0 & 0xffffff);
+    return (BCR0 & 0xffffff);
 }
 
 void pcm_mute(bool mute)
@@ -169,19 +171,32 @@
     if (!pcm_playing)
         return ;
 
-    if(pcm_paused && play && next_size)
+    if(pcm_paused && play)
     {
-        logf("unpause");
-        /* Reset chunk size so dma has enough data to fill the fifo. */
-        /* This shouldn't be needed anymore. */
-        //SAR0 = (unsigned long)next_start;
-        //BCR0 = next_size;
-        /* Enable the FIFO and force one write to it */
-        IIS2CONFIG = IIS_DEFPARM(pcm_freq);
+        if (BCR0 & 0xffffff)
+        {
+            logf("unpause");
+            /* Enable the FIFO and force one write to it */
+            IIS2CONFIG = IIS_DEFPARM(pcm_freq);
 #ifdef HAVE_SPDIF_OUT
-        EBU1CONFIG = EBU_DEFPARM;
+            EBU1CONFIG = EBU_DEFPARM;
 #endif
-        DCR0 |= DMA_EEXT | DMA_START;
+            DCR0 |= DMA_EEXT | DMA_START;
+        }
+        else
+        {
+            logf("unpause, no data waiting");
+            void (*get_more)(unsigned char**, size_t*) = callback_for_more;
+            if (get_more)
+                get_more(&next_start, &next_size);
+            if (next_start && next_size)
+                dma_start(next_start, next_size);
+            else
+            {
+                dma_stop();
+                logf("unpause attempted, no data");
+            }
+        }
     }
     else if(!pcm_paused && !play)
     {
@@ -224,13 +239,22 @@
     }
     else
     {
+        {
+            void (*get_more)(unsigned char**, size_t*) = callback_for_more;
+            if (get_more)
+                get_more(&next_start, &next_size);
+            else
+            {
+                next_size = 0;
+                next_start = NULL;
+            }
+        }
         if(next_size)
         {
             SAR0 = (unsigned long)next_start;  /* Source address */
             BCR0 = next_size;                  /* Bytes to transfer */
             DCR0 |= DMA_EEXT;
-            if (callback_for_more)
-                callback_for_more(&next_start, &next_size);
+
         }
         else
         {
@@ -301,9 +325,9 @@
 static int pcm_freq = 0x6; /* 44.1 is default */
 
 /* the registered callback function to ask for more mp3 data */
-static void (*callback_for_more)(unsigned char**, long*) = NULL;
+static void (*callback_for_more)(unsigned char**, size_t*) = NULL;
 static unsigned short *p IBSS_ATTR;
-static long size IBSS_ATTR;
+static size_t size IBSS_ATTR;
 
 /* Stops the DMA transfer and interrupt */
 static void dma_stop(void)
@@ -353,7 +377,7 @@
     IISCONFIG &= ~0x2;
 
     if ((size==0) && (callback_for_more)) {
-        callback_for_more((unsigned char **)&p, (long *)&size);
+        callback_for_more((unsigned char **)&p, &size);
     }
 
     while (size > 0) {
@@ -368,20 +392,22 @@
         size-=4;
 
         if ((size==0) && (callback_for_more)) {
-            callback_for_more((unsigned char **)&p, (long *)&size);
-	}
+            callback_for_more((unsigned char **)&p, &size);
+        }
     }
 }
 
-void pcm_play_data(void (*get_more)(unsigned char** start, long* size))
+void pcm_play_data(void (*get_more)(unsigned char** start, size_t* size),
+        unsigned char* _p, size_t _size)
 {
-    int free_count;
+    size_t free_count;
     
     callback_for_more = get_more;
 
     if (size > 0) { return; }
 
-    get_more((unsigned char **)&p, (long *)&size);
+    p = (unsigned short *)_p;
+    size = _size;
 
     /* setup I2S interrupt for FIQ */
     outl(inl(0x6000402c) | I2S_MASK, 0x6000402c);
@@ -406,7 +432,7 @@
         size-=4;
 
         if ((size==0) && (get_more)) {
-            get_more((unsigned char **)&p, (long *)&size);
+            get_more((unsigned char **)&p, &size);
 	}
     }
 }
@@ -448,7 +474,7 @@
     return pcm_playing;
 }
 
-long pcm_get_bytes_waiting(void)
+size_t pcm_get_bytes_waiting(void)
 {
     return size;
 }
@@ -608,9 +634,12 @@
     (void)frequency;
 }
 
-void pcm_play_data(void (*get_more)(unsigned char** start, long* size))
+void pcm_play_data(void (*get_more)(unsigned char** start, long* size),
+        unsigned char* start, long size)
 {
     (void)get_more;
+    (void)start;
+    (void)size;
 }
 
 void pcm_play_stop(void)