From 413da2a3d93d989d4474edad437ff67888487cb9 Mon Sep 17 00:00:00 2001 From: Brandon Low Date: Tue, 7 Feb 2006 20:38:55 +0000 Subject: [PATCH] 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 --- apps/codecs.h | 4 +- apps/debug_menu.c | 27 +- apps/dsp.c | 4 +- apps/dsp.h | 2 +- apps/pcmbuf.c | 723 ++++++++++++++++++--------------- apps/pcmbuf.h | 45 +- apps/playback.c | 109 +++-- apps/plugin.c | 68 ++-- apps/plugin.h | 90 ++-- apps/plugins/metronome.c | 12 +- apps/plugins/rockboy/rbsound.c | 4 +- firmware/export/pcm_playback.h | 5 +- firmware/pcm_playback.c | 105 +++-- 13 files changed, 667 insertions(+), 531 deletions(-) diff --git a/apps/codecs.h b/apps/codecs.h index 00f0f64733..cf0324b496 100644 --- a/apps/codecs.h +++ b/apps/codecs.h @@ -128,8 +128,8 @@ struct codec_api { 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 e7067cc055..4c36eaa113 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -209,7 +209,7 @@ extern int filebuflen; 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 @@ bool dbg_audio_thread(void) 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 @@ bool dbg_audio_thread(void) 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 @@ bool dbg_audio_thread(void) 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 @@ bool dbg_audio_thread(void) 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 @@ bool dbg_audio_thread(void) 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 e4d28bd083..1106f02679 100644 --- a/apps/dsp.c +++ b/apps/dsp.c @@ -213,7 +213,7 @@ void sound_set_pitch(int permille) * 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 @@ static void write_samples(short* dst, long* src[], int count) * 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 364c8d80f5..7e3acacc32 100644 --- a/apps/dsp.h +++ b/apps/dsp.h @@ -47,7 +47,7 @@ enum { 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 57058a3e95..cfb297e4f5 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 @@ enum { 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 @@ void pcmbuf_boost(bool state) 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 @@ void pcmbuf_set_boost_mode(bool state) } #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 @@ void pcmbuf_set_low_latency(bool state) 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 @@ bool pcmbuf_crossfade_init(bool manual_skip) 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 @@ void pcmbuf_play_stop(void) 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 @@ long pcmbuf_get_bufsize(void) /** 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 @@ void pcmbuf_flush_audio(void) 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 @@ static void crossfade_process_buffer( 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 @@ static void crossfade_process_buffer( *buf++; blocksize--; } - //yield(); } /* And finally set the mixing position where we should start fading in. */ @@ -425,12 +497,13 @@ static void crossfade_process_buffer( */ 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 @@ static void crossfade_start(void) 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 @@ static void crossfade_start(void) * 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 @@ static void crossfade_start(void) * 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 @@ static void fade_insert(const short *inbuf, int length) 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 @@ int crossfade(short *buf, const short *buf2, int length) 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 @@ static bool prepare_insert(long length) 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 @@ bool pcmbuf_is_crossfade_active(void) 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) +{ + while (pcm_is_playing()) + sleep(1); + pcm_play_data(NULL, &guardbuf[0], length); +} + +bool pcmbuf_insert_buffer(const char *buf, size_t length) { - long copy_n = 0; - 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 @@ bool pcmbuf_insert_buffer(char *buf, long length) 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 @@ int pcmbuf_mix_usage(void) 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 @@ void pcmbuf_crossfade_enable(bool on_off) 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 2476857f88..555c1bcb33 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_usage(void); 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 5ed6c5e00c..7688534e07 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -216,6 +216,13 @@ static bool v1first = false; 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 @@ static void voice_boost_cpu(bool state) #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 @@ bool codec_pcmbuf_insert_split_callback(void *ch1, void *ch2, } 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 @@ bool codec_pcmbuf_insert_split_callback(void *ch1, void *ch2, || pcmbuf_mix_usage() > 70) swap_codec(); } else { - pcmbuf_flush_buffer(output_size); + pcmbuf_write_complete(output_size); } break ; } @@ -379,7 +389,7 @@ bool codec_pcmbuf_insert_split_callback(void *ch1, void *ch2, 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 @@ void* get_codec_memory_callback(long *size) 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 @@ static void codec_track_changed(void) 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 @@ static void audio_update_trackinfo(void) /* Gapless playback. */ else { - pcmbuf_add_event(codec_track_changed); + pcmbuf_set_event_handler(pcmbuf_track_changed_callback); } } @@ -1675,15 +1693,17 @@ bool codec_request_next_track_callback(void) 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 @@ void audio_thread(void) 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 @@ void codec_thread(void) 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 @@ void audio_set_buffer_margin(int setting) /* 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 286c36cfb7..f5b33c65ce 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -123,6 +123,10 @@ static const struct plugin_api rockbox_api = { lcd_get_background, lcd_bitmap_part, lcd_bitmap, +#endif +#if LCD_DEPTH == 16 + lcd_bitmap_transparent_part, + lcd_bitmap_transparent, #endif lcd_putsxy, lcd_puts_style, @@ -198,6 +202,7 @@ static const struct plugin_api rockbox_api = { settings_parseline, #ifndef SIMULATOR ata_sleep, + ata_disk_is_active, #endif /* dir */ @@ -225,6 +230,17 @@ static const struct plugin_api rockbox_api = { 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 @@ static const struct plugin_api rockbox_api = { strncasecmp, memset, memcpy, + memmove, _ctype_, atoi, strchr, @@ -332,6 +349,23 @@ static const struct plugin_api rockbox_api = { 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 @@ static const struct plugin_api rockbox_api = { 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 @@ static const struct plugin_api rockbox_api = { /* 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 3d9161a456..286ca4087f 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 { @@ -161,6 +161,13 @@ struct plugin_api { int stride, int x, int y, int width, int height); 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); @@ -246,6 +253,7 @@ struct plugin_api { 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 @@ struct plugin_api { 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 @@ struct plugin_api { 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 @@ struct plugin_api { 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 @@ struct plugin_api { 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 @@ struct plugin_api { 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 @@ struct plugin_api { /* 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 2f897d72a2..ac4a990f61 100644 --- a/apps/plugins/metronome.c +++ b/apps/plugins/metronome.c @@ -736,18 +736,8 @@ void prepare_tock(void) } } -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 3eebea8bef..6371212ca8 100644 --- a/apps/plugins/rockboy/rbsound.c +++ b/apps/plugins/rockboy/rbsound.c @@ -35,7 +35,7 @@ static unsigned short *gmbuf; 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 @@ int pcm_submit(void) 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 5b61beb34d..a4cd93969b 100644 --- a/firmware/export/pcm_playback.h +++ b/firmware/export/pcm_playback.h @@ -23,10 +23,11 @@ void pcm_init(void); 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 0d9af14f2c..d3e9f3eca5 100644 --- a/firmware/pcm_playback.c +++ b/firmware/pcm_playback.c @@ -61,11 +61,11 @@ static bool pcm_playing; 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 @@ static void dma_stop(void) EBU1CONFIG = IIS_RESET | EBU_DEFPARM; #endif - next_start = NULL; - next_size = 0; pcm_paused = false; } @@ -131,23 +129,27 @@ void pcm_set_frequency(unsigned int frequency) } /* 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 @@ void pcm_play_pause(bool play) 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 @@ void DMA0(void) } 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 bool pcm_paused; 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 @@ void fiq(void) 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 @@ void fiq(void) 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 @@ void pcm_play_data(void (*get_more)(unsigned char** start, long* size)) size-=4; if ((size==0) && (get_more)) { - get_more((unsigned char **)&p, (long *)&size); + get_more((unsigned char **)&p, &size); } } } @@ -448,7 +474,7 @@ bool pcm_is_playing(void) return pcm_playing; } -long pcm_get_bytes_waiting(void) +size_t pcm_get_bytes_waiting(void) { return size; } @@ -608,9 +634,12 @@ void pcm_set_frequency(unsigned int frequency) (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)