pcmbuf: clarify and simplify crossfade code, etc.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@23538 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Jeffrey Goode 2009-11-05 21:59:36 +00:00
parent 013fe35992
commit 04b01e1831
5 changed files with 180 additions and 181 deletions

View file

@ -416,47 +416,7 @@ void codec_init_codec_api(void)
} }
/** track change functions */ /* track change */
static inline void codec_crossfade_track_change(void)
{
/* Initiate automatic crossfade mode */
pcmbuf_crossfade_init(false);
/* Notify the wps that the track change starts now */
LOGFQUEUE("codec > audio Q_AUDIO_TRACK_CHANGED");
queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0);
}
static void codec_track_skip_done(bool was_manual)
{
/* Manual track change (always crossfade or flush audio). */
if (was_manual)
{
pcmbuf_crossfade_init(true);
LOGFQUEUE("codec > audio Q_AUDIO_TRACK_CHANGED");
queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0);
}
/* Automatic track change w/crossfade, if not in "Track Skip Only" mode. */
else if (pcmbuf_is_crossfade_enabled() && !pcmbuf_is_crossfade_active()
&& global_settings.crossfade != CROSSFADE_ENABLE_TRACKSKIP)
{
if (global_settings.crossfade == CROSSFADE_ENABLE_SHUFFLE_AND_TRACKSKIP)
{
if (global_settings.playlist_shuffle)
/* shuffle mode is on, so crossfade: */
codec_crossfade_track_change();
else
/* shuffle mode is off, so normal gapless playback */
pcmbuf_start_track_change();
}
else
/* normal crossfade: */
codec_crossfade_track_change();
}
else
/* normal gapless playback. */
pcmbuf_start_track_change();
}
static bool codec_load_next_track(void) static bool codec_load_next_track(void)
{ {
@ -487,7 +447,7 @@ static bool codec_load_next_track(void)
{ {
case Q_CODEC_REQUEST_COMPLETE: case Q_CODEC_REQUEST_COMPLETE:
LOGFQUEUE("codec |< Q_CODEC_REQUEST_COMPLETE"); LOGFQUEUE("codec |< Q_CODEC_REQUEST_COMPLETE");
codec_track_skip_done(!automatic_skip); pcmbuf_start_track_change(!automatic_skip);
return true; return true;
case Q_CODEC_REQUEST_FAILED: case Q_CODEC_REQUEST_FAILED:

View file

@ -49,6 +49,17 @@ static inline int32_t clip_sample_16(int32_t sample)
return sample; return sample;
} }
#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_MIX_CHUNK 8192 /* This is the maximum size of one packet
for mixing (crossfade or voice) */
#if MEMORYSIZE > 2 #if MEMORYSIZE > 2
/* Keep watermark high for iPods at least (2s) */ /* Keep watermark high for iPods at least (2s) */
#define PCMBUF_WATERMARK (NATIVE_FREQUENCY * 4 * 2) #define PCMBUF_WATERMARK (NATIVE_FREQUENCY * 4 * 2)
@ -135,6 +146,10 @@ static bool prepare_insert(size_t length);
static void pcmbuf_under_watermark(bool under); static void pcmbuf_under_watermark(bool under);
static bool pcmbuf_flush_fillpos(void); static bool pcmbuf_flush_fillpos(void);
static bool pcmbuf_crossfade_init(bool manual_skip);
static bool pcmbuf_is_crossfade_enabled(void);
static void pcmbuf_finish_crossfade_enable(void);
/**************************************/ /**************************************/
@ -181,7 +196,7 @@ static bool show_desc(char *caller)
* still playing. Set flags to make sure the elapsed time of the current * still playing. Set flags to make sure the elapsed time of the current
* track is updated properly, and mark the currently written chunk as the * track is updated properly, and mark the currently written chunk as the
* last one in the track. */ * last one in the track. */
void pcmbuf_start_track_change(void) static void pcmbuf_gapless_track_change(void)
{ {
/* we're starting a track transition */ /* we're starting a track transition */
track_transition = true; track_transition = true;
@ -190,6 +205,44 @@ void pcmbuf_start_track_change(void)
end_of_track = true; end_of_track = true;
} }
static void pcmbuf_crossfade_track_change(void)
{
/* Initiate automatic crossfade mode */
pcmbuf_crossfade_init(false);
/* Notify the wps that the track change starts now */
audio_post_track_change(false);
}
void pcmbuf_start_track_change(bool manual_skip)
{
/* Manual track change (always crossfade or flush audio). */
if (manual_skip)
{
pcmbuf_crossfade_init(true);
audio_post_track_change(false);
}
/* Automatic track change w/crossfade, if not in "Track Skip Only" mode. */
else if (pcmbuf_is_crossfade_enabled() && !pcmbuf_is_crossfade_active()
&& global_settings.crossfade != CROSSFADE_ENABLE_TRACKSKIP)
{
if (global_settings.crossfade == CROSSFADE_ENABLE_SHUFFLE_AND_TRACKSKIP)
{
if (global_settings.playlist_shuffle)
/* shuffle mode is on, so crossfade: */
pcmbuf_crossfade_track_change();
else
/* shuffle mode is off, so normal gapless playback */
pcmbuf_gapless_track_change();
}
else
/* normal crossfade: */
pcmbuf_crossfade_track_change();
}
else
/* normal gapless playback. */
pcmbuf_gapless_track_change();
}
/* Called when the last chunk in the track has been played */ /* Called when the last chunk in the track has been played */
static void pcmbuf_finish_track_change(void) static void pcmbuf_finish_track_change(void)
{ {
@ -198,7 +251,7 @@ static void pcmbuf_finish_track_change(void)
track_transition = false; track_transition = false;
/* notify playback that the track has just finished */ /* notify playback that the track has just finished */
audio_post_track_change(); audio_post_track_change(true);
} }
@ -274,15 +327,6 @@ static void pcmbuf_pcm_callback(unsigned char** start, size_t* size)
DISPLAY_DESC("callback"); DISPLAY_DESC("callback");
} }
static void pcmbuf_set_watermark_bytes(void)
{
pcmbuf_watermark = (crossfade_enabled && pcmbuf_size) ?
/* If crossfading, try to keep the buffer full other than 1 second */
(pcmbuf_size - (NATIVE_FREQUENCY * 4 * 1)) :
/* Otherwise, just use the default */
PCMBUF_WATERMARK;
}
/* This is really just part of pcmbuf_flush_fillpos, but is easier to keep /* This is really just part of pcmbuf_flush_fillpos, but is easier to keep
* in a separate function for the moment */ * in a separate function for the moment */
static inline void pcmbuf_add_chunk(void) static inline void pcmbuf_add_chunk(void)
@ -330,6 +374,28 @@ static inline void pcmbuf_add_chunk(void)
DISPLAY_DESC("add_chunk"); DISPLAY_DESC("add_chunk");
} }
/**
* Commit samples waiting to the pcm buffer.
*/
static bool pcmbuf_flush_fillpos(void)
{
if (audiobuffer_fillpos) {
/* Never use the last buffer descriptor */
while (pcmbuf_write == pcmbuf_write_end) {
/* If this happens, something is being stupid */
if (!pcm_is_playing()) {
logf("pcmbuf_flush_fillpos error");
pcmbuf_play_start();
}
/* Let approximately one chunk of data playback */
sleep(HZ*PCMBUF_TARGET_CHUNK/(NATIVE_FREQUENCY*4));
}
pcmbuf_add_chunk();
return true;
}
return false;
}
#ifdef HAVE_PRIORITY_SCHEDULING #ifdef HAVE_PRIORITY_SCHEDULING
static void boost_codec_thread(bool boost) static void boost_codec_thread(bool boost)
{ {
@ -431,7 +497,7 @@ inline size_t pcmbuf_free(void)
return pcmbuf_size; return pcmbuf_size;
} }
bool pcmbuf_crossfade_init(bool manual_skip) static bool pcmbuf_crossfade_init(bool manual_skip)
{ {
/* Can't do two crossfades at once and, no fade if pcm is off now */ /* Can't do two crossfades at once and, no fade if pcm is off now */
if (crossfade_init || crossfade_active || !pcm_is_playing()) if (crossfade_init || crossfade_active || !pcm_is_playing())
@ -542,11 +608,20 @@ static char *pcmbuf_calc_audiobuffer_ptr(size_t bufsize)
bool pcmbuf_is_same_size(void) bool pcmbuf_is_same_size(void)
{ {
if (audiobuffer == NULL) bool same_size;
return true; /* Not set up yet even once so always */
if (audiobuffer == NULL)
same_size = true; /* Not set up yet even once so always */
else
{
size_t bufsize = pcmbuf_get_next_required_pcmbuf_size(); size_t bufsize = pcmbuf_get_next_required_pcmbuf_size();
return pcmbuf_calc_audiobuffer_ptr(bufsize) == audiobuffer; same_size = pcmbuf_calc_audiobuffer_ptr(bufsize) == audiobuffer;
}
if (same_size)
pcmbuf_finish_crossfade_enable();
return same_size;
} }
/* Initialize the pcmbuffer the structure looks like this: /* Initialize the pcmbuffer the structure looks like this:
@ -566,7 +641,7 @@ size_t pcmbuf_init(unsigned char *bufend)
end_of_track = false; end_of_track = false;
track_transition = false; track_transition = false;
pcmbuf_crossfade_enable_finished(); pcmbuf_finish_crossfade_enable();
pcmbuf_play_stop(); pcmbuf_play_stop();
@ -606,28 +681,6 @@ void pcmbuf_play_start(void)
} }
} }
/**
* Commit samples waiting to the pcm buffer.
*/
static bool pcmbuf_flush_fillpos(void)
{
if (audiobuffer_fillpos) {
/* Never use the last buffer descriptor */
while (pcmbuf_write == pcmbuf_write_end) {
/* If this happens, something is being stupid */
if (!pcm_is_playing()) {
logf("pcmbuf_flush_fillpos error");
pcmbuf_play_start();
}
/* Let approximately one chunk of data playback */
sleep(HZ*PCMBUF_TARGET_CHUNK/(NATIVE_FREQUENCY*4));
}
pcmbuf_add_chunk();
return true;
}
return false;
}
/** /**
* Low memory targets don't have crossfade, so don't compile crossfade * Low memory targets don't have crossfade, so don't compile crossfade
* specific code in order to save some memory. */ * specific code in order to save some memory. */
@ -943,38 +996,6 @@ void* pcmbuf_request_buffer(int *count)
} }
} }
void * pcmbuf_request_voice_buffer(int *count)
{
/* A get-it-to-work-for-now hack (audio status could change by
completion) */
if (audio_status() & AUDIO_STATUS_PLAY)
{
if (pcmbuf_read == NULL)
{
return NULL;
}
else if (pcmbuf_usage() >= 10 && pcmbuf_mix_free() >= 30 &&
(pcmbuf_mix_chunk || pcmbuf_read->link))
{
*count = MIN(*count, PCMBUF_MIX_CHUNK/4);
return voicebuf;
}
else
{
return NULL;
}
}
else
{
return pcmbuf_request_buffer(count);
}
}
bool pcmbuf_is_crossfade_active(void)
{
return crossfade_active || crossfade_init;
}
void pcmbuf_write_complete(int count) void pcmbuf_write_complete(int count)
{ {
size_t length = (size_t)(unsigned int)count << 2; size_t length = (size_t)(unsigned int)count << 2;
@ -1098,12 +1119,12 @@ void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude)
#endif /* HAVE_HARDWARE_BEEP */ #endif /* HAVE_HARDWARE_BEEP */
/* Returns pcm buffer usage in percents (0 to 100). */ /* Returns pcm buffer usage in percents (0 to 100). */
int pcmbuf_usage(void) static int pcmbuf_usage(void)
{ {
return pcmbuf_unplayed_bytes * 100 / pcmbuf_size; return pcmbuf_unplayed_bytes * 100 / pcmbuf_size;
} }
int pcmbuf_mix_free(void) static int pcmbuf_mix_free(void)
{ {
if (pcmbuf_mix_chunk) if (pcmbuf_mix_chunk)
{ {
@ -1117,6 +1138,33 @@ int pcmbuf_mix_free(void)
return 100; return 100;
} }
void *pcmbuf_request_voice_buffer(int *count)
{
/* A get-it-to-work-for-now hack (audio status could change by
completion) */
if (audio_status() & AUDIO_STATUS_PLAY)
{
if (pcmbuf_read == NULL)
{
return NULL;
}
else if (pcmbuf_usage() >= 10 && pcmbuf_mix_free() >= 30 &&
(pcmbuf_mix_chunk || pcmbuf_read->link))
{
*count = MIN(*count, PCMBUF_MIX_CHUNK/4);
return voicebuf;
}
else
{
return NULL;
}
}
else
{
return pcmbuf_request_buffer(count);
}
}
void pcmbuf_write_voice_complete(int count) void pcmbuf_write_voice_complete(int count)
{ {
/* A get-it-to-work-for-now hack (audio status could have changed) */ /* A get-it-to-work-for-now hack (audio status could have changed) */
@ -1163,23 +1211,33 @@ void pcmbuf_write_voice_complete(int count)
} }
} }
void pcmbuf_crossfade_enable(bool on_off) void pcmbuf_request_crossfade_enable(bool on_off)
{ {
/* Next setting to be used, not applied now */ /* Next setting to be used, not applied now */
crossfade_enabled_pending = on_off; crossfade_enabled_pending = on_off;
} }
void pcmbuf_crossfade_enable_finished(void) static void pcmbuf_finish_crossfade_enable(void)
{ {
/* Copy the pending setting over now */ /* Copy the pending setting over now */
crossfade_enabled = crossfade_enabled_pending; crossfade_enabled = crossfade_enabled_pending;
pcmbuf_set_watermark_bytes();
pcmbuf_watermark = (crossfade_enabled && pcmbuf_size) ?
/* If crossfading, try to keep the buffer full other than 1 second */
(pcmbuf_size - (NATIVE_FREQUENCY * 4 * 1)) :
/* Otherwise, just use the default */
PCMBUF_WATERMARK;
} }
bool pcmbuf_is_crossfade_enabled(void) static bool pcmbuf_is_crossfade_enabled(void)
{ {
if (global_settings.crossfade == CROSSFADE_ENABLE_SHUFFLE) if (global_settings.crossfade == CROSSFADE_ENABLE_SHUFFLE)
return global_settings.playlist_shuffle; return global_settings.playlist_shuffle;
return crossfade_enabled; return crossfade_enabled;
} }
bool pcmbuf_is_crossfade_active(void)
{
return crossfade_active || crossfade_init;
}

View file

@ -21,58 +21,37 @@
#ifndef PCMBUF_H #ifndef PCMBUF_H
#define PCMBUF_H #define PCMBUF_H
#define PCMBUF_TARGET_CHUNK 32768 /* This is the target fill size of chunks /* playback */
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_MIX_CHUNK 8192 /* This is the maximum size of one packet
for mixing (crossfade or voice) */
/* Returns true if the buffer needs to change size */
bool pcmbuf_is_same_size(void);
size_t pcmbuf_init(unsigned char *bufend); size_t pcmbuf_init(unsigned char *bufend);
/* Size in bytes used by the pcmbuffer */ void pcmbuf_play_start(void);
void pcmbuf_play_stop(void);
void pcmbuf_pause(bool pause);
void pcmbuf_start_track_change(bool manual_skip);
void *pcmbuf_request_buffer(int *count);
void pcmbuf_write_complete(int count);
/* crossfade */
bool pcmbuf_is_crossfade_active(void);
void pcmbuf_request_crossfade_enable(bool on_off);
bool pcmbuf_is_same_size(void);
/* voice */
void *pcmbuf_request_voice_buffer(int *count);
void pcmbuf_write_voice_complete(int count);
/* debug menu, other metrics */
size_t pcmbuf_free(void);
size_t pcmbuf_get_bufsize(void); size_t pcmbuf_get_bufsize(void);
int pcmbuf_descs(void);
int pcmbuf_used_descs(void);
#ifdef ROCKBOX_HAS_LOGF #ifdef ROCKBOX_HAS_LOGF
/* just used for logging for now */
unsigned char *pcmbuf_get_meminfo(size_t *length); unsigned char *pcmbuf_get_meminfo(size_t *length);
#endif #endif
void pcmbuf_pause(bool pause); /* misc */
void pcmbuf_play_stop(void);
bool pcmbuf_is_crossfade_active(void);
/* These functions are for playing chained buffers of PCM data */
#if defined(HAVE_ADJUSTABLE_CPU_FREQ)
void pcmbuf_boost(bool state);
void pcmbuf_set_boost_mode(bool state);
#else
#define pcmbuf_boost(state) do { } while(0)
#define pcmbuf_set_boost_mode(state) do { } while(0)
#endif
bool pcmbuf_is_lowdata(void);
void pcmbuf_play_start(void);
bool pcmbuf_crossfade_init(bool manual_skip);
void pcmbuf_start_track_change(void);
size_t pcmbuf_free(void);
unsigned long pcmbuf_get_latency(void);
void pcmbuf_set_low_latency(bool state);
void * pcmbuf_request_buffer(int *count);
void pcmbuf_write_complete(int count);
void * pcmbuf_request_voice_buffer(int *count);
void pcmbuf_write_voice_complete(int count);
bool pcmbuf_is_crossfade_enabled(void);
void pcmbuf_crossfade_enable(bool on_off);
void pcmbuf_crossfade_enable_finished(void);
int pcmbuf_usage(void);
int pcmbuf_mix_free(void);
void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude); void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude);
bool pcmbuf_is_lowdata(void);
int pcmbuf_used_descs(void); void pcmbuf_set_low_latency(bool state);
int pcmbuf_descs(void); unsigned long pcmbuf_get_latency(void);
#endif #endif

View file

@ -246,11 +246,19 @@ void audio_pcmbuf_position_callback(size_t size)
/* Post message from pcmbuf that the end of the previous track /* Post message from pcmbuf that the end of the previous track
* has just been played. */ * has just been played. */
void audio_post_track_change(void) void audio_post_track_change(bool pcmbuf)
{
if (pcmbuf)
{ {
LOGFQUEUE("pcmbuf > pcmbuf Q_AUDIO_TRACK_CHANGED"); LOGFQUEUE("pcmbuf > pcmbuf Q_AUDIO_TRACK_CHANGED");
queue_post(&pcmbuf_queue, Q_AUDIO_TRACK_CHANGED, 0); queue_post(&pcmbuf_queue, Q_AUDIO_TRACK_CHANGED, 0);
} }
else
{
LOGFQUEUE("pcmbuf > audio Q_AUDIO_TRACK_CHANGED");
queue_post(&audio_queue, Q_AUDIO_TRACK_CHANGED, 0);
}
}
/* Scan the pcmbuf queue and return true if a message pulled. /* Scan the pcmbuf queue and return true if a message pulled.
* Permissible Context(s): Thread * Permissible Context(s): Thread
@ -814,18 +822,12 @@ void audio_set_crossfade(int enable)
size_t size; size_t size;
/* Tell it the next setting to use */ /* Tell it the next setting to use */
pcmbuf_crossfade_enable(enable); pcmbuf_request_crossfade_enable(enable);
/* Return if size hasn't changed or this is too early to determine /* Return if size hasn't changed or this is too early to determine
which in the second case there's no way we could be playing which in the second case there's no way we could be playing
anything at all */ anything at all */
if (pcmbuf_is_same_size()) if (pcmbuf_is_same_size()) return;
{
/* This function is a copout and just syncs some variables -
to be removed at a later date */
pcmbuf_crossfade_enable_finished();
return;
}
offset = 0; offset = 0;
was_playing = playing; was_playing = playing;
@ -2058,7 +2060,7 @@ void audio_init(void)
#endif #endif
/* Set crossfade setting for next buffer init which should be about... */ /* Set crossfade setting for next buffer init which should be about... */
pcmbuf_crossfade_enable(global_settings.crossfade); pcmbuf_request_crossfade_enable(global_settings.crossfade);
/* initialize the buffering system */ /* initialize the buffering system */

View file

@ -70,7 +70,7 @@ enum
bool audio_restore_playback(int type); /* Restores the audio buffer to handle the requested playback */ bool audio_restore_playback(int type); /* Restores the audio buffer to handle the requested playback */
size_t audio_get_filebuflen(void); size_t audio_get_filebuflen(void);
void audio_pcmbuf_position_callback(size_t size) ICODE_ATTR; void audio_pcmbuf_position_callback(size_t size) ICODE_ATTR;
void audio_post_track_change(void); void audio_post_track_change(bool pcmbuf);
int get_audio_hid(void); int get_audio_hid(void);
int *get_codec_hid(void); int *get_codec_hid(void);
void audio_set_prev_elapsed(unsigned long setting); void audio_set_prev_elapsed(unsigned long setting);