summaryrefslogtreecommitdiffstats
path: root/apps/pcmbuf.c
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2011-08-28 07:45:35 +0000
committerMichael Sevakis <jethead71@rockbox.org>2011-08-28 07:45:35 +0000
commit7ad2cad173ffa094bb285112582afee1c9aea4e5 (patch)
treece23e816cfdffb1767ebe44f4f960c304d8a5fb9 /apps/pcmbuf.c
parent463b3ed8b2630d1b9d656dd2a52bbcbd429b4c08 (diff)
downloadrockbox-7ad2cad173ffa094bb285112582afee1c9aea4e5.tar.gz
rockbox-7ad2cad173ffa094bb285112582afee1c9aea4e5.tar.bz2
rockbox-7ad2cad173ffa094bb285112582afee1c9aea4e5.zip
Commit work started in FS#12153 to put timing/position information in PCM
buffer chunks. * Samples and position indication is closely associated with audio data instead of compensating by a latency constant. Alleviates problems with using the elapsed as a track indicator where it could be off by several steps. * Timing is accurate throughout track even if resampling for pitch shift, whereas before it updated during transition latency at the normal 1:1 rate. * Simpler PCM buffer with a constant chunk size, no linked lists. In converting crossfade, a minor change was made to not change the WPS until the fade-in of the incoming track, whereas before it would change upon the start of the fade-out of the outgoing track possibly having the WPS change with far too much lead time. Codec changes are to set elapsed times *before* writing next PCM frame because time and position data last set are saved in the next committed PCM chunk. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30366 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/pcmbuf.c')
-rw-r--r--apps/pcmbuf.c1415
1 files changed, 762 insertions, 653 deletions
diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c
index 8736fe2ae2..f57021d237 100644
--- a/apps/pcmbuf.c
+++ b/apps/pcmbuf.c
@@ -8,6 +8,7 @@
* $Id$
*
* Copyright (C) 2005 by Miika Pekkarinen
+ * Copyright (C) 2011 by Michael Sevakis
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -35,59 +36,69 @@
#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
#include "cpu.h"
#endif
-#include <string.h>
#include "settings.h"
#include "audio.h"
#include "voice_thread.h"
#include "dsp.h"
-#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 CROSSFADE_BUFSIZE 8192 /* Size of the crossfade buffer */
-
-/* number of bytes played per second (sample rate * 2 channels * 2 bytes/sample) */
+/* This is the target fill size of chunks on the pcm buffer
+ Can be any number of samples but power of two sizes make for faster and
+ smaller math - must be < 65536 bytes */
+#define PCMBUF_CHUNK_SIZE 8192u
+#define PCMBUF_GUARD_SIZE 1024u
+
+/* Mnemonics for common data commit thresholds */
+#define COMMIT_CHUNKS PCMBUF_CHUNK_SIZE
+#define COMMIT_ALL_DATA 1u
+
+ /* Size of the crossfade buffer where codec data is written to be faded
+ on commit */
+#define CROSSFADE_BUFSIZE 8192u
+
+/* Number of bytes played per second:
+ (sample rate * 2 channels * 2 bytes/sample) */
#define BYTERATE (NATIVE_FREQUENCY * 4)
#if MEMORYSIZE > 2
-/* Keep watermark high for iPods at least (2s) */
+/* Keep watermark high for large memory target - at least (2s) */
#define PCMBUF_WATERMARK (BYTERATE * 2)
+#define MIN_BUFFER_SIZE (BYTERATE * 3)
#else
#define PCMBUF_WATERMARK (BYTERATE / 4) /* 0.25 seconds */
+#define MIN_BUFFER_SIZE (BYTERATE * 1)
#endif
-/* Structure we can use to queue pcm chunks in memory to be played
- * by the driver code. */
+/* Describes each audio packet - keep it small since there are many of them */
struct chunkdesc
{
- unsigned char *addr;
- size_t size;
- struct chunkdesc* link;
- /* true if last chunk in the track */
- bool end_of_track;
+ uint16_t size; /* Actual size (0 < size <= PCMBUF_CHUNK_SIZE) */
+ uint8_t is_end; /* Flag indicating end of track */
+ uint8_t pos_key; /* Who put the position info in (0 = undefined) */
+ unsigned long elapsed; /* Elapsed time to use */
+ off_t offset; /* Offset to use */
};
-#define NUM_CHUNK_DESCS(bufsize) \
- ((bufsize) / PCMBUF_MINAVG_CHUNK)
+/* General PCM buffer data */
+#define INVALID_BUF_INDEX ((size_t)0 - (size_t)1)
+
+static unsigned char *pcmbuf_buffer;
+static unsigned char *pcmbuf_guardbuf;
+static size_t pcmbuf_size;
+static struct chunkdesc *pcmbuf_descriptors;
+static unsigned int pcmbuf_desc_count;
+static unsigned int position_key = 1;
+
+static size_t chunk_ridx;
+static size_t chunk_widx;
+
+static size_t pcmbuf_bytes_waiting;
-/* Size of the PCM buffer. */
-static size_t pcmbuf_size = 0;
-static char *pcmbuf_bufend;
-static char *pcmbuffer;
-/* Current PCM buffer write index. */
-static size_t pcmbuffer_pos;
-/* Amount pcmbuffer_pos will be increased.*/
-static size_t pcmbuffer_fillpos;
+static size_t pcmbuf_watermark;
+static struct chunkdesc *current_desc;
-static struct chunkdesc *first_desc;
+static bool low_latency_mode = false;
-/* Gapless playback */
-static bool track_transition;
+static bool pcmbuf_sync_position = false;
/* Fade effect */
static unsigned int fade_vol = MIX_AMP_UNITY;
@@ -104,184 +115,179 @@ static bool soft_mode = false;
#ifdef HAVE_CROSSFADE
/* Crossfade buffer */
-static char *fadebuf;
+static unsigned char *crossfade_buffer;
/* Crossfade related state */
-static bool crossfade_enabled;
-static bool crossfade_enable_request;
+static int crossfade_setting;
+static int crossfade_enable_request;
static bool crossfade_mixmode;
static bool crossfade_auto_skip;
-static bool crossfade_active;
-static bool crossfade_track_change_started;
+
+static enum
+{
+ CROSSFADE_INACTIVE = 0,
+ CROSSFADE_TRACK_CHANGE_STARTED,
+ CROSSFADE_ACTIVE,
+} crossfade_status = CROSSFADE_INACTIVE;
/* Track the current location for processing crossfade */
-static struct chunkdesc *crossfade_chunk;
-static size_t crossfade_sample;
+static size_t crossfade_index;
/* Counters for fading in new data */
static size_t crossfade_fade_in_total;
static size_t crossfade_fade_in_rem;
-#endif
-static struct chunkdesc *read_chunk;
-static struct chunkdesc *read_end_chunk;
-static struct chunkdesc *write_chunk;
-static struct chunkdesc *write_end_chunk;
-static size_t last_chunksize;
-
-static size_t pcmbuf_unplayed_bytes;
-static size_t pcmbuf_watermark;
+/* Defines for operations on position info when mixing/fading -
+ passed in offset parameter */
+enum
+{
+ MIXFADE_KEEP_POS = -1, /* Keep position info in chunk */
+ MIXFADE_NULLIFY_POS = -2, /* Ignore position info in chunk */
+ /* Positive values cause stamping/restamping */
+};
-static bool low_latency_mode = false;
-static bool flush_pcmbuf = false;
+static void crossfade_start(void);
+static void write_to_crossfade(size_t size, unsigned long elapsed,
+ off_t offset);
+static void pcmbuf_finish_crossfade_enable(void);
+#endif /* HAVE_CROSSFADE */
+/* Thread */
#ifdef HAVE_PRIORITY_SCHEDULING
static int codec_thread_priority = PRIORITY_PLAYBACK;
#endif
/* Helpful macros for use in conditionals this assumes some of the above
* static variable names */
-#define COMMIT_IF_NEEDED if(pcmbuffer_fillpos > PCMBUF_TARGET_CHUNK || \
- (pcmbuffer_pos + pcmbuffer_fillpos) >= pcmbuf_size) commit_chunk(false)
-#define LOW_DATA(quarter_secs) \
- (pcmbuf_unplayed_bytes < NATIVE_FREQUENCY * quarter_secs)
-
-#ifdef HAVE_CROSSFADE
-static void crossfade_start(void);
-static void write_to_crossfade(size_t length);
-static void pcmbuf_finish_crossfade_enable(void);
-#endif
+#define DATA_LEVEL(quarter_secs) (NATIVE_FREQUENCY * (quarter_secs))
/* Callbacks into playback.c */
-extern void audio_pcmbuf_position_callback(unsigned int time);
+extern void audio_pcmbuf_position_callback(unsigned long elapsed,
+ off_t offset, unsigned int key);
extern void audio_pcmbuf_track_change(bool pcmbuf);
extern bool audio_pcmbuf_may_play(void);
+extern void audio_pcmbuf_sync_position(void);
/**************************************/
-/* define this to show detailed chunkdesc usage information on the sim console */
-/*#define DESC_DEBUG*/
+/* Return number of commited bytes in buffer (committed chunks count as
+ a full chunk even if only partially filled) */
+static size_t pcmbuf_unplayed_bytes(void)
+{
+ size_t ridx = chunk_ridx;
+ size_t widx = chunk_widx;
+
+ if (ridx > widx)
+ widx += pcmbuf_size;
-#ifndef SIMULATOR
-#undef DESC_DEBUG
-#endif
+ return widx - ridx;
+}
+
+/* Return the next PCM chunk in the PCM buffer given a byte index into it */
+static size_t index_next(size_t index)
+{
+ index = ALIGN_DOWN(index + PCMBUF_CHUNK_SIZE, PCMBUF_CHUNK_SIZE);
+
+ if (index >= pcmbuf_size)
+ index -= pcmbuf_size;
+
+ return index;
+}
-#ifdef DESC_DEBUG
-#define DISPLAY_DESC(caller) while(!show_desc(caller))
-#define DESC_IDX(desc) (desc ? desc - first_desc : -1)
-#define SHOW_DESC(desc) if(DESC_IDX(desc)==-1) DEBUGF("--"); \
- else DEBUGF("%02d", DESC_IDX(desc))
-#define SHOW_DESC_LINK(desc) if(desc){SHOW_DESC(desc->link);DEBUGF(" ");} \
- else DEBUGF("-- ")
-#define SHOW_DETAIL(desc) DEBUGF(":");SHOW_DESC(desc); DEBUGF(">"); \
- SHOW_DESC_LINK(desc)
-#define SHOW_POINT(tag,desc) DEBUGF("%s",tag);SHOW_DETAIL(desc)
-#define SHOW_NUM(num,desc) DEBUGF("%02d>",num);SHOW_DESC_LINK(desc)
-
-static bool show_desc(char *caller)
+/* Convert a byte offset in the PCM buffer into a pointer in the buffer */
+static FORCE_INLINE void * index_buffer(size_t index)
{
- if (show_desc_in_use) return false;
- show_desc_in_use = true;
- DEBUGF("%-14s\t", caller);
- SHOW_POINT("r", read_chunk);
- SHOW_POINT("re", read_end_chunk);
- DEBUGF(" ");
- SHOW_POINT("w", write_chunk);
- SHOW_POINT("we", write_end_chunk);
- DEBUGF("\n");
- int i;
- for (i = 0; i < pcmbuf_descs(); i++)
+ return pcmbuf_buffer + index;
+}
+
+/* Convert a pointer in the buffer into an index offset */
+static FORCE_INLINE size_t buffer_index(void *p)
+{
+ return (uintptr_t)p - (uintptr_t)pcmbuf_buffer;
+}
+
+/* Return a chunk descriptor for a byte index in the buffer */
+static struct chunkdesc * index_chunkdesc(size_t index)
+{
+ return &pcmbuf_descriptors[index / PCMBUF_CHUNK_SIZE];
+}
+
+/* Return a chunk descriptor for a byte index in the buffer, offset by 'offset'
+ chunks */
+static struct chunkdesc * index_chunkdesc_offs(size_t index, int offset)
+{
+ int i = index / PCMBUF_CHUNK_SIZE;
+
+ if (offset != 0)
{
- SHOW_NUM(i, (first_desc + i));
- if (i%10 == 9) DEBUGF("\n");
+ i = (i + offset) % pcmbuf_desc_count;
+
+ /* remainder => modulus */
+ if (i < 0)
+ i += pcmbuf_desc_count;
}
- DEBUGF("\n\n");
- show_desc_in_use = false;
- return true;
+
+ return &pcmbuf_descriptors[i];
}
-#else
-#define DISPLAY_DESC(caller) do{}while(0)
-#endif
/** Accept new PCM data */
-/* Commit PCM buffer samples as a new chunk for playback */
-static void commit_chunk(bool flush_next_time)
+/* Split the uncommitted data as needed into chunks, stopping when uncommitted
+ data is below the threshold */
+static void commit_chunks(size_t threshold)
{
- if (!pcmbuffer_fillpos)
- return;
-
- /* Never use the last buffer descriptor */
- while (write_chunk == write_end_chunk) {
- /* If this happens, something is being stupid */
- if (!pcm_is_playing()) {
- logf("commit_chunk error");
- pcmbuf_play_start();
- }
- /* Let approximately one chunk of data playback */
- sleep(HZ * PCMBUF_TARGET_CHUNK / BYTERATE);
- }
+ size_t index = chunk_widx;
+ size_t end_index = index + pcmbuf_bytes_waiting;
- /* commit the chunk */
+ /* Copy to the beginning of the buffer all data that must wrap */
+ if (end_index > pcmbuf_size)
+ memcpy(pcmbuf_buffer, pcmbuf_guardbuf, end_index - pcmbuf_size);
- register size_t size = pcmbuffer_fillpos;
- /* Grab the next description to write, and change the write pointer */
- register struct chunkdesc *pcmbuf_current = write_chunk;
- write_chunk = pcmbuf_current->link;
- /* Fill in the values in the new buffer chunk */
- pcmbuf_current->addr = &pcmbuffer[pcmbuffer_pos];
- pcmbuf_current->size = size;
- pcmbuf_current->end_of_track = false;
- pcmbuf_current->link = NULL;
+ struct chunkdesc *desc = index_chunkdesc(index);
- if (read_chunk != NULL)
+ do
{
- if (flush_pcmbuf)
- {
- /* Flush! Discard all data after the currently playing chunk,
- and make the current chunk play next */
- logf("commit_chunk: flush");
- pcm_play_lock();
- write_end_chunk->link = read_chunk->link;
- read_chunk->link = pcmbuf_current;
- while (write_end_chunk->link)
- {
- write_end_chunk = write_end_chunk->link;
- pcmbuf_unplayed_bytes -= write_end_chunk->size;
- }
+ size_t size = MIN(pcmbuf_bytes_waiting, PCMBUF_CHUNK_SIZE);
+ pcmbuf_bytes_waiting -= size;
- read_chunk->end_of_track = track_transition;
- pcm_play_unlock();
- }
- /* If there is already a read buffer setup, add to it */
- else
- read_end_chunk->link = pcmbuf_current;
- }
- else
- {
- /* Otherwise create the buffer */
- read_chunk = pcmbuf_current;
- }
+ /* Fill in the values in the new buffer chunk */
+ desc->size = (uint16_t)size;
- /* If flush_next_time is true, then the current chunk will be thrown out
- * and the next chunk to be committed will be the next to be played.
- * This is used to empty the PCM buffer for a track change. */
- flush_pcmbuf = flush_next_time;
+ /* Advance the current write chunk and make it available to the
+ PCM callback */
+ chunk_widx = index = index_next(index);
+ desc = index_chunkdesc(index);
- /* This is now the last buffer to read */
- read_end_chunk = pcmbuf_current;
+ /* Reset it before using it */
+ desc->is_end = 0;
+ desc->pos_key = 0;
+ }
+ while (pcmbuf_bytes_waiting >= threshold);
+}
- /* Update bytes counters */
- pcmbuf_unplayed_bytes += size;
+/* If uncommitted data count is above or equal to the threshold, commit it */
+static FORCE_INLINE void commit_if_needed(size_t threshold)
+{
+ if (pcmbuf_bytes_waiting >= threshold)
+ commit_chunks(threshold);
+}
- pcmbuffer_pos += size;
- if (pcmbuffer_pos >= pcmbuf_size)
- pcmbuffer_pos -= pcmbuf_size;
+/* Place positioning information in the chunk */
+static void stamp_chunk(struct chunkdesc *desc, unsigned long elapsed,
+ off_t offset)
+{
+ /* One-time stamping of a given chunk by the same track - new track may
+ overwrite */
+ unsigned int key = position_key;
- pcmbuffer_fillpos = 0;
- DISPLAY_DESC("commit_chunk");
+ if (desc->pos_key != key)
+ {
+ desc->pos_key = key;
+ desc->elapsed = elapsed;
+ desc->offset = offset;
+ }
}
/* Set priority of the codec thread */
@@ -290,7 +296,8 @@ static void commit_chunk(bool flush_next_time)
* expects pcm_fill_state in tenth-% units (e.g. full pcm buffer is 10) */
static void boost_codec_thread(int pcm_fill_state)
{
- static const int prios[11] = {
+ static const int8_t prios[11] =
+ {
PRIORITY_PLAYBACK_MAX, /* 0 - 10% */
PRIORITY_PLAYBACK_MAX+1, /* 10 - 20% */
PRIORITY_PLAYBACK_MAX+3, /* 20 - 30% */
@@ -298,12 +305,13 @@ static void boost_codec_thread(int pcm_fill_state)
PRIORITY_PLAYBACK_MAX+7, /* 40 - 50% */
PRIORITY_PLAYBACK_MAX+8, /* 50 - 60% */
PRIORITY_PLAYBACK_MAX+9, /* 60 - 70% */
- /* raiseing priority above 70% shouldn't be needed */
+ /* raising priority above 70% shouldn't be needed */
PRIORITY_PLAYBACK, /* 70 - 80% */
PRIORITY_PLAYBACK, /* 80 - 90% */
PRIORITY_PLAYBACK, /* 90 -100% */
PRIORITY_PLAYBACK, /* 100% */
};
+
int new_prio = prios[pcm_fill_state];
/* Keep voice and codec threads at the same priority or else voice
@@ -319,37 +327,86 @@ static void boost_codec_thread(int pcm_fill_state)
#define boost_codec_thread(pcm_fill_state) do{}while(0)
#endif /* HAVE_PRIORITY_SCHEDULING */
-/* Return true if the PCM buffer is able to receive new data.
- * Also maintain buffer level above the watermark. */
-static bool prepare_insert(size_t length)
+/* Get the next available buffer and size - assumes adequate space exists */
+static void * get_write_buffer(size_t *size)
{
- bool playing = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_STOPPED;
+ /* Obtain current chunk fill address */
+ size_t index = chunk_widx + pcmbuf_bytes_waiting;
+ size_t index_end = pcmbuf_size + PCMBUF_GUARD_SIZE;
- if (low_latency_mode)
- {
- /* 1/4s latency. */
- if (!LOW_DATA(1) && playing)
- return false;
- }
+ /* Get count to the end of the buffer where a wrap will happen +
+ the guard */
+ size_t endsize = index_end - index;
- /* Need to save PCMBUF_MIN_CHUNK to prevent wrapping overwriting */
- if (pcmbuf_free() < length + PCMBUF_MIN_CHUNK)
- return false;
+ /* Return available unwrapped space */
+ *size = MIN(*size, endsize);
+
+ return index_buffer(index);
+}
+
+/* Commit outstanding data leaving less than a chunk size remaining and
+ write position info to the first chunk */
+static void commit_write_buffer(size_t size, unsigned long elapsed, off_t offset)
+{
+ struct chunkdesc *desc = index_chunkdesc(chunk_widx);
+ stamp_chunk(desc, elapsed, offset);
+
+ /* Add this data and commit if one or more chunks are ready */
+ pcmbuf_bytes_waiting += size;
+
+ commit_if_needed(COMMIT_CHUNKS);
+}
+
+/* Request space in the buffer for writing output samples */
+void * pcmbuf_request_buffer(int *count)
+{
+ size_t size = *count * 4;
+
+#ifdef HAVE_CROSSFADE
+ /* We're going to crossfade to a new track, which is now on its way */
+ if (crossfade_status == CROSSFADE_TRACK_CHANGE_STARTED)
+ crossfade_start();
+
+ /* If crossfade has begun, put the new track samples in crossfade_buffer */
+ if (crossfade_status != CROSSFADE_INACTIVE && size > CROSSFADE_BUFSIZE)
+ size = CROSSFADE_BUFSIZE;
+#endif
+
+ enum channel_status status = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK);
+ size_t remaining = pcmbuf_unplayed_bytes();
+
+ /* Need to have length bytes to prevent wrapping overwriting - leave one
+ descriptor free to guard so that 0 != full in ring buffer */
+ size_t freespace = pcmbuf_free();
+
+ if (pcmbuf_sync_position)
+ audio_pcmbuf_sync_position();
+
+ if (freespace < size + PCMBUF_CHUNK_SIZE)
+ return NULL;
/* Maintain the buffer level above the watermark */
- if (playing)
+ if (status != CHANNEL_STOPPED)
{
- /* boost cpu if necessary */
- if (pcmbuf_unplayed_bytes < pcmbuf_watermark)
+ if (low_latency_mode)
+ {
+ /* 1/4s latency. */
+ if (remaining > DATA_LEVEL(1))
+ return NULL;
+ }
+
+ /* Boost CPU if necessary */
+ size_t realrem = pcmbuf_size - freespace;
+
+ if (realrem < pcmbuf_watermark)
trigger_cpu_boost();
- boost_codec_thread(pcmbuf_unplayed_bytes*10/pcmbuf_size);
+
+ boost_codec_thread(realrem*10 / pcmbuf_size);
#ifdef HAVE_CROSSFADE
/* Disable crossfade if < .5s of audio */
- if (LOW_DATA(2))
- {
- crossfade_active = false;
- }
+ if (remaining < DATA_LEVEL(2))
+ crossfade_status = CROSSFADE_INACTIVE;
#endif
}
else /* !playing */
@@ -359,378 +416,344 @@ static bool prepare_insert(size_t length)
/* If pre-buffered to the watermark, start playback */
#if MEMORYSIZE > 2
- if (!LOW_DATA(4))
+ if (remaining > DATA_LEVEL(4))
#else
- if (pcmbuf_unplayed_bytes > pcmbuf_watermark)
+ if (remaining > pcmbuf_watermark)
#endif
{
- logf("pcm starting");
if (audio_pcmbuf_may_play())
pcmbuf_play_start();
}
}
- return true;
-}
-
-/* Request space in the buffer for writing output samples */
-void *pcmbuf_request_buffer(int *count)
-{
+ void *buf =
#ifdef HAVE_CROSSFADE
- /* we're going to crossfade to a new track, which is now on its way */
- if (crossfade_track_change_started)
- crossfade_start();
-
- /* crossfade has begun, put the new track samples in fadebuf */
- if (crossfade_active)
- {
- int cnt = MIN(*count, CROSSFADE_BUFSIZE/4);
- if (prepare_insert(cnt << 2))
- {
- *count = cnt;
- return fadebuf;
- }
- }
- else
+ crossfade_status != CROSSFADE_INACTIVE ? crossfade_buffer :
#endif
- /* if possible, reserve room in the PCM buffer for new samples */
- {
- if(prepare_insert(*count << 2))
- {
- size_t pcmbuffer_index = pcmbuffer_pos + pcmbuffer_fillpos;
- if (pcmbuf_size - pcmbuffer_index >= PCMBUF_MIN_CHUNK)
- {
- /* Usual case, there's space here */
- return &pcmbuffer[pcmbuffer_index];
- }
- else
- {
- /* Wrap the buffer, the new samples go at the beginning */
- commit_chunk(false);
- pcmbuffer_pos = 0;
- return &pcmbuffer[0];
- }
- }
- }
- /* PCM buffer not ready to receive new data yet */
- return NULL;
+ get_write_buffer(&size);
+
+ *count = size / 4;
+ return buf;
}
/* Handle new samples to the buffer */
-void pcmbuf_write_complete(int count)
+void pcmbuf_write_complete(int count, unsigned long elapsed, off_t offset)
{
- size_t length = (size_t)(unsigned int)count << 2;
+ size_t size = count * 4;
+
#ifdef HAVE_CROSSFADE
- if (crossfade_active)
- write_to_crossfade(length);
+ if (crossfade_status != CROSSFADE_INACTIVE)
+ {
+ write_to_crossfade(size, elapsed, offset);
+ }
else
#endif
{
- pcmbuffer_fillpos += length;
- COMMIT_IF_NEEDED;
+ commit_write_buffer(size, elapsed, offset);
}
+
+ /* Revert to position updates by PCM */
+ pcmbuf_sync_position = false;
}
/** Init */
-
-static inline void init_pcmbuffers(void)
-{
- first_desc = write_chunk;
- struct chunkdesc *next = write_chunk;
- next++;
- write_end_chunk = write_chunk;
- while ((void *)next < (void *)pcmbuf_bufend) {
- write_end_chunk->link=next;
- write_end_chunk=next;
- next++;
- }
- DISPLAY_DESC("init");
-}
-
-static size_t get_next_required_pcmbuf_size(void)
+static unsigned int get_next_required_pcmbuf_chunks(void)
{
- size_t seconds = 1;
+ size_t size = MIN_BUFFER_SIZE;
#ifdef HAVE_CROSSFADE
- if (crossfade_enable_request)
- seconds += global_settings.crossfade_fade_out_delay +
- global_settings.crossfade_fade_out_duration;
+ if (crossfade_enable_request != CROSSFADE_ENABLE_OFF)
+ {
+ size_t seconds = global_settings.crossfade_fade_out_delay +
+ global_settings.crossfade_fade_out_duration;
+ size += seconds * BYTERATE;
+ }
#endif
-#if MEMORYSIZE > 2
- /* Buffer has to be at least 2s long. */
- seconds += 2;
-#endif
- logf("pcmbuf len: %ld", (long)seconds);
- return seconds * BYTERATE;
+ logf("pcmbuf len: %lu", (unsigned long)(size / BYTERATE));
+ return size / PCMBUF_CHUNK_SIZE;
+}
+
+/* Initialize the ringbuffer state */
+static void init_buffer_state(void)
+{
+ /* Reset counters */
+ chunk_ridx = chunk_widx = 0;
+ pcmbuf_bytes_waiting = 0;
+
+ /* Reset first descriptor */
+ struct chunkdesc *desc = pcmbuf_descriptors;
+ desc->is_end = 0;
+ desc->pos_key = 0;
}
-/* Initialize the pcmbuffer the structure looks like this:
- * ...|---------PCMBUF---------[|FADEBUF]|DESCS|... */
+/* Initialize the PCM buffer. The structure looks like this:
+ * ...[|FADEBUF]|---------PCMBUF---------|GUARDBUF|DESCS| */
size_t pcmbuf_init(unsigned char *bufend)
{
- pcmbuf_bufend = bufend;
- pcmbuf_size = get_next_required_pcmbuf_size();
- write_chunk = (struct chunkdesc *)pcmbuf_bufend -
- NUM_CHUNK_DESCS(pcmbuf_size);
+ unsigned char *bufstart;
-#ifdef HAVE_CROSSFADE
- fadebuf = (unsigned char *)write_chunk -
- (crossfade_enable_request ? CROSSFADE_BUFSIZE : 0);
- pcmbuffer = fadebuf - pcmbuf_size;
-#else
- pcmbuffer = (unsigned char *)write_chunk - pcmbuf_size;
-#endif
+ /* Set up the buffers */
+ pcmbuf_desc_count = get_next_required_pcmbuf_chunks();
+ pcmbuf_size = pcmbuf_desc_count * PCMBUF_CHUNK_SIZE;
+ pcmbuf_descriptors = (struct chunkdesc *)bufend - pcmbuf_desc_count;
+
+ pcmbuf_buffer = (void *)pcmbuf_descriptors -
+ pcmbuf_size - PCMBUF_GUARD_SIZE;
- init_pcmbuffers();
+ /* Mem-align buffer chunks for more efficient handling in lower layers */
+ pcmbuf_buffer = ALIGN_DOWN(pcmbuf_buffer, (uintptr_t)MEM_ALIGN_SIZE);
+
+ pcmbuf_guardbuf = pcmbuf_buffer + pcmbuf_size;
+ bufstart = pcmbuf_buffer;
#ifdef HAVE_CROSSFADE
+ /* Allocate FADEBUF if it will be needed */
+ if (crossfade_enable_request != CROSSFADE_ENABLE_OFF)
+ {
+ bufstart -= CROSSFADE_BUFSIZE;
+ crossfade_buffer = bufstart;
+ }
+
pcmbuf_finish_crossfade_enable();
-#else
+#else /* !HAVE_CROSSFADE */
pcmbuf_watermark = PCMBUF_WATERMARK;
-#endif
+#endif /* HAVE_CROSSFADE */
- pcmbuf_play_stop();
+ init_buffer_state();
pcmbuf_soft_mode(false);
- return pcmbuf_bufend - pcmbuffer;
+ return bufend - bufstart;
}
/** Track change */
-void pcmbuf_monitor_track_change(bool monitor)
-{
- pcm_play_lock();
- if (last_chunksize != 0)
+/* Place a track change notification in a specific descriptor or post it
+ immediately if the buffer is empty or the index is invalid */
+static void pcmbuf_monitor_track_change_ex(size_t index, int offset)
+{
+ if (chunk_ridx != chunk_widx && index != INVALID_BUF_INDEX)
{
- /* If monitoring, wait until this track runs out. Place in
- currently playing chunk. If not, cancel notification. */
- track_transition = monitor;
- read_end_chunk->end_of_track = monitor;
- if (!monitor)
- {
- /* Clear all notifications */
- struct chunkdesc *desc = first_desc;
- struct chunkdesc *end = desc + pcmbuf_descs();
- while (desc < end)
- desc++->end_of_track = false;
- }
+ /* If monitoring, set flag in specified chunk */
+ index_chunkdesc_offs(index, offset)->is_end = 1;
}
else
{
- /* Post now if PCM stopped and last buffer was sent. */
- track_transition = false;
- if (monitor)
- audio_pcmbuf_track_change(false);
+ /* Post now if no outstanding buffers exist */
+ audio_pcmbuf_track_change(false);
}
+}
+
+/* Clear end of track and optionally the positioning info for all data */
+static void pcmbuf_cancel_track_change(bool position)
+{
+ size_t index = chunk_ridx;
+
+ while (1)
+ {
+ struct chunkdesc *desc = index_chunkdesc(index);
+
+ desc->is_end = 0;
+
+ if (position)
+ desc->pos_key = 0;
+
+ if (index == chunk_widx)
+ break;
+
+ index = index_next(index);
+ }
+}
+
+/* Place a track change notification at the end of the buffer or post it
+ immediately if the buffer is empty */
+void pcmbuf_monitor_track_change(bool monitor)
+{
+ pcm_play_lock();
+
+ if (monitor)
+ pcmbuf_monitor_track_change_ex(chunk_widx, -1);
+ else
+ pcmbuf_cancel_track_change(false);
pcm_play_unlock();
}
-bool pcmbuf_start_track_change(bool auto_skip)
+void pcmbuf_start_track_change(enum pcm_track_change_type type)
{
- bool crossfade = false;
#ifdef HAVE_CROSSFADE
- /* Determine whether this track change needs to crossfade */
- if(crossfade_enabled && !pcmbuf_is_crossfade_active())
- {
- switch(global_settings.crossfade)
- {
- case CROSSFADE_ENABLE_AUTOSKIP:
- crossfade = auto_skip;
- break;
- case CROSSFADE_ENABLE_MANSKIP:
- crossfade = !auto_skip;
- break;
- case CROSSFADE_ENABLE_SHUFFLE:
- crossfade = global_settings.playlist_shuffle;
- break;
- case CROSSFADE_ENABLE_SHUFFLE_OR_MANSKIP:
- crossfade = global_settings.playlist_shuffle || !auto_skip;
- break;
- case CROSSFADE_ENABLE_ALWAYS:
- crossfade = true;
- break;
- }
- }
+ bool crossfade = false;
#endif
+ bool auto_skip = type != TRACK_CHANGE_MANUAL;
- if (!auto_skip || crossfade)
- /* manual skip or crossfade */
- {
- if (crossfade)
- { logf(" crossfade track change"); }
- else
- { logf(" manual track change"); }
+ /* Commit all outstanding data before starting next track - tracks don't
+ comingle inside a single buffer chunk */
+ commit_if_needed(COMMIT_ALL_DATA);
- pcm_play_lock();
+ /* Update position key so that:
+ 1) Positions are keyed to the track to which they belong for sync
+ purposes
- /* Cancel any pending automatic gapless transition */
- pcmbuf_monitor_track_change(false);
+ 2) Buffers stamped with the outgoing track's positions are restamped
+ to the incoming track's positions when crossfading
+ */
+ if (++position_key > UINT8_MAX)
+ position_key = 1;
- /* Can't do two crossfades at once and, no fade if pcm is off now */
- if (
+ if (type == TRACK_CHANGE_END_OF_DATA)
+ {
+ /* If end of all data, force playback */
+ if (audio_pcmbuf_may_play())
+ pcmbuf_play_start();
+ }
#ifdef HAVE_CROSSFADE
- pcmbuf_is_crossfade_active() ||
-#endif
- mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_STOPPED)
+ /* Determine whether this track change needs to crossfaded and how */
+ else if (crossfade_setting != CROSSFADE_ENABLE_OFF &&
+ !pcmbuf_is_crossfade_active() &&
+ pcmbuf_unplayed_bytes() >= DATA_LEVEL(2) &&
+ !low_latency_mode)
+ {
+ switch (crossfade_setting)
{
- pcmbuf_play_stop();
- pcm_play_unlock();
- /* Notify playback that the track change starts now */
- return true;
+ case CROSSFADE_ENABLE_AUTOSKIP:
+ crossfade = auto_skip;
+ break;
+ case CROSSFADE_ENABLE_MANSKIP:
+ crossfade = !auto_skip;
+ break;
+ case CROSSFADE_ENABLE_SHUFFLE:
+ crossfade = global_settings.playlist_shuffle;
+ break;
+ case CROSSFADE_ENABLE_SHUFFLE_OR_MANSKIP:
+ crossfade = global_settings.playlist_shuffle || !auto_skip;
+ break;
+ case CROSSFADE_ENABLE_ALWAYS:
+ crossfade = true;
+ break;
}
+ }
+ /* else crossfade is off, crossfade is already active, not enough data,
+ * pcm is off now (implying low data), not crossfading or low latency mode
+ */
- /* Not enough data, or not crossfading, flush the old data instead */
- if (LOW_DATA(2) || !crossfade || low_latency_mode)
- {
- commit_chunk(true);
- }
-#ifdef HAVE_CROSSFADE
- else
- {
- /* Don't enable mix mode when skipping tracks manually. */
- crossfade_mixmode = auto_skip &&
- global_settings.crossfade_fade_out_mixmode;
+ if (crossfade)
+ {
+ logf("crossfade track change");
- crossfade_auto_skip = auto_skip;
+ /* Don't enable mix mode when skipping tracks manually */
+ crossfade_mixmode = auto_skip &&
+ global_settings.crossfade_fade_out_mixmode;
- crossfade_track_change_started = crossfade;
- }
-#endif
- pcm_play_unlock();
+ crossfade_auto_skip = auto_skip;
+
+ crossfade_status = CROSSFADE_TRACK_CHANGE_STARTED;
- /* Keep trigger outside the play lock or HW FIFO underruns can happen
- since frequency scaling is *not* always fast */
trigger_cpu_boost();
- /* Notify playback that the track change starts now */
- return true;
+ /* Cancel any pending automatic gapless transition and if a manual
+ skip, stop position updates */
+ pcm_play_lock();
+ pcmbuf_cancel_track_change(!auto_skip);
+ pcm_play_unlock();
}
- else /* automatic and not crossfading, so do gapless track change */
+ else
+#endif /* HAVE_CROSSFADE */
+ if (auto_skip)
{
/* The codec is moving on to the next track, but the current track will
- * continue to play. Set a flag to make sure the elapsed time of the
- * current track will be updated properly, and mark the current chunk
- * as the last one in the track. */
- logf(" gapless track change");
+ * continue to play, so mark the last write chunk as the last one in
+ * the track */
+ logf("gapless track change");
+#ifdef HAVE_CROSSFADE
+ if (crossfade_status != CROSSFADE_INACTIVE)
+ {
+ /* Crossfade is still active but crossfade is not happening - for
+ * now, chicken-out and clear out the buffer (just like before) to
+ * avoid fade pile-up on short tracks fading-in over long ones */
+ pcmbuf_play_stop();
+ }
+#endif
pcmbuf_monitor_track_change(true);
- return false;
+ }
+ else
+ {
+ /* Discard old data; caller needs no transition notification */
+ logf("manual track change");
+ pcmbuf_play_stop();
}
}
/** Playback */
-/* PCM driver callback
- * This function has 3 major logical parts (separated by brackets both for
- * readability and variable scoping). The first part performs the
- * operations related to finishing off the last chunk we fed to the DMA.
- * The second part detects the end of playlist condition when the PCM
- * buffer is empty except for uncommitted samples. Then they are committed
- * and sent to the PCM driver for playback. The third part performs the
- * operations involved in sending a new chunk to the DMA. */
-static void pcmbuf_pcm_callback(unsigned char** start, size_t* size)
+/* PCM driver callback */
+static void pcmbuf_pcm_callback(unsigned char **start, size_t *size)
{
- struct chunkdesc *pcmbuf_current = read_chunk;
+ /*- Process the chunk that just finished -*/
+ size_t index = chunk_ridx;
+ struct chunkdesc *desc = current_desc;
+ if (desc)
{
- /* Take the finished chunk out of circulation */
- read_chunk = pcmbuf_current->link;
-
- /* if during a track transition, update the elapsed time in ms */
- if (track_transition)
- audio_pcmbuf_position_callback(last_chunksize * 1000 / BYTERATE);
-
- /* if last chunk in the track, stop updates and notify audio thread */
- if (pcmbuf_current->end_of_track)
- {
- track_transition = false;
+ /* If last chunk in the track, notify of track change */
+ if (desc->is_end != 0)
audio_pcmbuf_track_change(true);
- }
-
- /* Put the finished chunk back into circulation */
- write_end_chunk->link = pcmbuf_current;
- write_end_chunk = pcmbuf_current;
-#ifdef HAVE_CROSSFADE
- /* If we've read over the crossfade chunk while it's still fading */
- if (pcmbuf_current == crossfade_chunk)
- crossfade_chunk = read_chunk;
-#endif
+ /* Free it for reuse */
+ chunk_ridx = index = index_next(index);
}
+ /*- Process the new one -*/
+ if (index != chunk_widx && !fade_out_complete)
{
- /* Commit last samples at end of playlist */
- if (pcmbuffer_fillpos && !pcmbuf_current)
- {
- logf("pcmbuf_pcm_callback: commit last samples");
- commit_chunk(false);
- }
- }
+ current_desc = desc = index_chunkdesc(index);
- /* Stop at this frame */
- pcmbuf_current = fade_out_complete ? NULL : read_chunk;
+ *start = index_buffer(index);
+ *size = desc->size;
- {
- /* Send the new chunk to the DMA */
- if(pcmbuf_current)
- {
- last_chunksize = pcmbuf_current->size;
- pcmbuf_unplayed_bytes -= last_chunksize;
- *size = last_chunksize;
- *start = pcmbuf_current->addr;
- }
- else
+ if (desc->pos_key != 0)
{
- /* No more chunks or pause indicated */
- logf("pcmbuf_pcm_callback: no more chunks");
- last_chunksize = 0;
- *size = 0;
- *start = NULL;
+ /* Positioning chunk - notify playback */
+ audio_pcmbuf_position_callback(desc->elapsed, desc->offset,
+ desc->pos_key);
}
}
- DISPLAY_DESC("callback");
}
/* Force playback */
void pcmbuf_play_start(void)
{
+ logf("pcmbuf_play_start");
+
if (mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_STOPPED &&
- pcmbuf_unplayed_bytes && read_chunk != NULL)
+ chunk_widx != chunk_ridx)
{
- logf("pcmbuf_play_start");
- last_chunksize = read_chunk->size;
- pcmbuf_unplayed_bytes -= last_chunksize;
+ current_desc = NULL;
mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, pcmbuf_pcm_callback,
- read_chunk->addr, last_chunksize);
+ NULL, 0);
}
}
+/* Stop channel, empty and reset buffer */
void pcmbuf_play_stop(void)
{
logf("pcmbuf_play_stop");
+
+ /* Reset channel */
mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK);
- pcmbuf_unplayed_bytes = 0;
- if (read_chunk) {
- write_end_chunk->link = read_chunk;
- write_end_chunk = read_end_chunk;
- read_chunk = read_end_chunk = NULL;
- }
- last_chunksize = 0;
- pcmbuffer_pos = 0;
- pcmbuffer_fillpos = 0;
+ /* Reset buffer */
+ init_buffer_state();
+
+ /* Revert to position updates by PCM */
+ pcmbuf_sync_position = false;
+
#ifdef HAVE_CROSSFADE
- crossfade_track_change_started = false;
- crossfade_active = false;
+ crossfade_status = CROSSFADE_INACTIVE;
#endif
- track_transition = false;
- flush_pcmbuf = false;
- DISPLAY_DESC("play_stop");
/* Can unboost the codec thread here no matter who's calling,
* pretend full pcm buffer to unboost */
@@ -750,74 +773,153 @@ void pcmbuf_pause(bool pause)
/** Crossfade */
-/* Clip sample to signed 16 bit range */
-static inline int32_t clip_sample_16(int32_t sample)
+#ifdef HAVE_CROSSFADE
+/* Find the buffer index that's 'size' bytes away from 'index' */
+static size_t crossfade_find_index(size_t index, size_t size)
{
- if ((int16_t)sample != sample)
- sample = 0x7fff ^ (sample >> 31);
- return sample;
+ if (index != INVALID_BUF_INDEX)
+ {
+ size_t i = ALIGN_DOWN(index, PCMBUF_CHUNK_SIZE);
+ size += index - i;
+
+ while (i != chunk_widx)
+ {
+ size_t desc_size = index_chunkdesc(i)->size;
+
+ if (size < desc_size)
+ return i + size;
+
+ size -= desc_size;
+ i = index_next(i);
+ }
+ }
+
+ return INVALID_BUF_INDEX;
}
-#ifdef HAVE_CROSSFADE
-/* Find the chunk that's (length) deep in the list. Return the position within
- * the chunk, and leave the chunkdesc pointer pointing to the chunk. */
-static size_t find_chunk(size_t length, struct chunkdesc **chunk)
+/* Align the needed buffer area up to the end of existing data */
+static size_t crossfade_find_buftail(size_t buffer_rem, size_t buffer_need)
{
- while (*chunk && length >= (*chunk)->size)
+ crossfade_index = chunk_ridx;
+
+ if (buffer_rem > buffer_need)
{
- length -= (*chunk)->size;
- *chunk = (*chunk)->link;
+ size_t distance;
+
+ if (crossfade_auto_skip)
+ {
+ /* Automatic track changes only modify the last part of the buffer,
+ * so find the right chunk and sample to start the crossfade */
+ distance = buffer_rem - buffer_need;
+ buffer_rem = buffer_need;
+ }
+ else
+ {
+ /* Manual skips occur immediately, but give 1/5s to process */
+ distance = BYTERATE / 5;
+ buffer_rem -= BYTERATE / 5;
+ }
+
+ crossfade_index = crossfade_find_index(crossfade_index, distance);
}
- return length;
+
+ return buffer_rem;
+}
+
+/* Clip sample to signed 16 bit range */
+static FORCE_INLINE int32_t clip_sample_16(int32_t sample)
+{
+ if ((int16_t)sample != sample)
+ sample = 0x7fff ^ (sample >> 31);
+ return sample;
}
/* Returns the number of bytes _NOT_ mixed/faded */
-static size_t crossfade_mix_fade(int factor, size_t length, const char *buf,
- size_t *out_sample, struct chunkdesc **out_chunk)
+static int crossfade_mix_fade(int factor, size_t size, void *buf, size_t *out_index,
+ unsigned long elapsed, off_t offset)
{
- if (length == 0)
+ if (size == 0)
return 0;
- const int16_t *input_buf = (const int16_t *)buf;
- int16_t *output_buf = (int16_t *)((*out_chunk)->addr);
- int16_t *chunk_end = SKIPBYTES(output_buf, (*out_chunk)->size);
- output_buf = &output_buf[*out_sample];
- int32_t sample;
+ size_t index = *out_index;
+
+ if (index == INVALID_BUF_INDEX)
+ return size;
+
+ const int16_t *input_buf = buf;
+ int16_t *output_buf = (int16_t *)index_buffer(index);
- while (length)
+ while (size)
{
- /* fade left and right channel at once to keep buffer alignment */
- int i;
- for (i = 0; i < 2; i++)
+ struct chunkdesc *desc = index_chunkdesc(index);
+
+ switch (offset)
{
+ case MIXFADE_NULLIFY_POS:
+ /* Stop position updates for the chunk */
+ desc->pos_key = 0;
+ break;
+ case MIXFADE_KEEP_POS:
+ /* Keep position info as it is */
+ break;
+ default:
+ /* Replace position info */
+ stamp_chunk(desc, elapsed, offset);
+ }
+
+ size_t rem = desc->size - (index % PCMBUF_CHUNK_SIZE);
+ int16_t *chunk_end = SKIPBYTES(output_buf, rem);
+
+ if (size < rem)
+ rem = size;
+
+ size -= rem;
+
+ do
+ {
+ /* fade left and right channel at once to keep buffer alignment */
+ int32_t left = output_buf[0];
+ int32_t right = output_buf[1];
+
if (input_buf)
- /* fade the input buffer and mix into the chunk */
{
- sample = *input_buf++;
- sample = ((sample * factor) >> 8) + *output_buf;
- *output_buf++ = clip_sample_16(sample);
+ /* fade the input buffer and mix into the chunk */
+ left += *input_buf++ * factor >> 8;
+ right += *input_buf++ * factor >> 8;
+ left = clip_sample_16(left);
+ right = clip_sample_16(right);
}
else
- /* fade the chunk only */
{
- sample = *output_buf;
- *output_buf++ = (sample * factor) >> 8;
+ /* fade the chunk only */
+ left = left * factor >> 8;
+ right = right * factor >> 8;
}
- }
- length -= 4; /* 2 samples, each 16 bit -> 4 bytes */
+ *output_buf++ = left;
+ *output_buf++ = right;
+
+ rem -= 4;
+ }
+ while (rem);
/* move to next chunk as needed */
if (output_buf >= chunk_end)
{
- *out_chunk = (*out_chunk)->link;
- if (!(*out_chunk))
- return length;
- output_buf = (int16_t *)((*out_chunk)->addr);
- chunk_end = SKIPBYTES(output_buf, (*out_chunk)->size);
+ index = index_next(index);
+
+ if (index == chunk_widx)
+ {
+ /* End of existing data */
+ *out_index = INVALID_BUF_INDEX;
+ return size;
+ }
+
+ output_buf = (int16_t *)index_buffer(index);
}
}
- *out_sample = output_buf - (int16_t *)((*out_chunk)->addr);
+
+ *out_index = buffer_index(output_buf);
return 0;
}
@@ -825,184 +927,209 @@ static size_t crossfade_mix_fade(int factor, size_t length, const char *buf,
* fade-out with the PCM buffer. */
static void crossfade_start(void)
{
- size_t crossfade_rem;
- size_t crossfade_need;
- size_t fade_out_rem;
- size_t fade_out_delay;
- size_t fade_in_delay;
-
- crossfade_track_change_started = false;
- /* Reject crossfade if less than .5s of data */
- if (LOW_DATA(2)) {
- logf("crossfade rejected");
- pcmbuf_play_stop();
- return ;
- }
-
logf("crossfade_start");
- commit_chunk(false);
- crossfade_active = true;
+
+ pcm_play_lock();
/* 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;
- crossfade_chunk = read_chunk->link;
- crossfade_sample = 0;
-
- /* Get fade out info from settings. */
- fade_out_delay = global_settings.crossfade_fade_out_delay * BYTERATE;
- fade_out_rem = global_settings.crossfade_fade_out_duration * BYTERATE;
+ size_t unplayed = pcmbuf_unplayed_bytes();
- crossfade_need = fade_out_delay + fade_out_rem;
- if (crossfade_rem > crossfade_need)
+ /* Reject crossfade if less than .5s of data */
+ if (unplayed < DATA_LEVEL(2))
{
+ logf("crossfade rejected");
+
+ crossfade_status = CROSSFADE_INACTIVE;
+
if (crossfade_auto_skip)
- /* Automatic track changes only modify the last part of the buffer,
- * so find the right chunk and sample to start the crossfade */
- {
- crossfade_sample = find_chunk(crossfade_rem - crossfade_need,
- &crossfade_chunk) / 2;
- crossfade_rem = crossfade_need;
- }
- else
- /* Manual skips occur immediately, but give time to process */
- {
- crossfade_rem -= crossfade_chunk->size;
- crossfade_chunk = crossfade_chunk->link;
- }
+ pcmbuf_monitor_track_change(true);
+
+ pcm_play_unlock();
+ return;
}
- /* Truncate fade out duration if necessary. */
- if (crossfade_rem < crossfade_need)
+
+ /* Fading will happen */
+ crossfade_status = CROSSFADE_ACTIVE;
+
+ /* Get fade info from settings. */
+ size_t fade_out_delay = global_settings.crossfade_fade_out_delay * BYTERATE;
+ size_t fade_out_rem = global_settings.crossfade_fade_out_duration * BYTERATE;
+ size_t fade_in_delay = global_settings.crossfade_fade_in_delay * BYTERATE;
+ size_t fade_in_duration = global_settings.crossfade_fade_in_duration * BYTERATE;
+
+ if (!crossfade_auto_skip)
{
- size_t crossfade_short = crossfade_need - crossfade_rem;
- if (fade_out_rem >= crossfade_short)
- fade_out_rem -= crossfade_short;
+ /* Forego fade-in delay on manual skip - do the best to preserve auto skip
+ relationship */
+ if (fade_out_delay > fade_in_delay)
+ fade_out_delay -= fade_in_delay;
else
- {
- fade_out_delay -= crossfade_short - fade_out_rem;
- fade_out_rem = 0;
- }
+ fade_out_delay = 0;
+
+ fade_in_delay = 0;
}
- crossfade_rem -= fade_out_delay + fade_out_rem;
- /* Completely process the crossfade fade-out effect with current PCM buffer */
+ size_t fade_out_need = fade_out_delay + fade_out_rem;
+
if (!crossfade_mixmode)
{
+ size_t buffer_rem = crossfade_find_buftail(unplayed, fade_out_need);
+
+ pcm_play_unlock();
+
+ if (buffer_rem < fade_out_need)
+ {
+ /* Existing buffers are short */
+ size_t fade_out_short = fade_out_need - buffer_rem;
+
+ if (fade_out_rem >= fade_out_short)
+ {
+ /* Truncate fade-out duration */
+ fade_out_rem -= fade_out_short;
+ }
+ else
+ {
+ /* Truncate fade-out and fade-out delay */
+ fade_out_delay = fade_out_rem;
+ fade_out_rem = 0;
+ }
+ }
+
+ /* Completely process the crossfade fade-out effect with current PCM buffer */
+
/* Fade out the specified amount of the already processed audio */
- size_t total_fade_out = fade_out_rem;
- size_t fade_out_sample;
- struct chunkdesc *fade_out_chunk = crossfade_chunk;
+ size_t fade_out_total = fade_out_rem;
/* Find the right chunk and sample to start fading out */
- fade_out_delay += crossfade_sample * 2;
- fade_out_sample = find_chunk(fade_out_delay, &fade_out_chunk) / 2;
+ size_t fade_out_index = crossfade_find_index(crossfade_index, fade_out_delay);
while (fade_out_rem > 0)
{
- /* Each 1/10 second of audio will have the same fade applied */
- size_t block_rem = MIN(BYTERATE / 10, fade_out_rem);
- int factor = (fade_out_rem << 8) / total_fade_out;
+ /* Each 1/20 second of audio will have the same fade applied */
+ size_t block_rem = MIN(BYTERATE / 20, fade_out_rem);
+ int factor = (fade_out_rem << 8) / fade_out_total;
fade_out_rem -= block_rem;
- crossfade_mix_fade(factor, block_rem, NULL,
- &fade_out_sample, &fade_out_chunk);
+ crossfade_mix_fade(factor, block_rem, NULL, &fade_out_index,
+ 0, MIXFADE_KEEP_POS);
}
/* zero out the rest of the buffer */
- crossfade_mix_fade(0, crossfade_rem, NULL,
- &fade_out_sample, &fade_out_chunk);
+ crossfade_mix_fade(0, INT_MAX, NULL, &fade_out_index,
+ 0, MIXFADE_NULLIFY_POS);
+
+ pcm_play_lock();
}
/* Initialize fade-in counters */
- crossfade_fade_in_total = global_settings.crossfade_fade_in_duration * BYTERATE;
- crossfade_fade_in_rem = crossfade_fade_in_total;
+ crossfade_fade_in_total = fade_in_duration;
+ crossfade_fade_in_rem = fade_in_duration;
- fade_in_delay = global_settings.crossfade_fade_in_delay * BYTERATE;
+ /* Find the right chunk and sample to start fading in - redo from read
+ chunk in case original position were/was overrun in callback - the
+ track change event _must not_ ever fail to happen */
+ unplayed = pcmbuf_unplayed_bytes() + fade_in_delay;
+
+ crossfade_find_buftail(unplayed, fade_out_need);
+
+ if (crossfade_auto_skip)
+ pcmbuf_monitor_track_change_ex(crossfade_index, 0);
+
+ pcm_play_unlock();
- /* Find the right chunk and sample to start fading in */
- fade_in_delay += crossfade_sample * 2;
- crossfade_sample = find_chunk(fade_in_delay, &crossfade_chunk) / 2;
logf("crossfade_start done!");
}
/* Perform fade-in of new track */
-static void write_to_crossfade(size_t length)
+static void write_to_crossfade(size_t size, unsigned long elapsed, off_t offset)
{
- if (length)
- {
- char *buf = fadebuf;
- if (crossfade_fade_in_rem)
- {
- size_t samples;
- int16_t *input_buf;
+ unsigned char *buf = crossfade_buffer;
- /* Fade factor for this packet */
- int factor =
- ((crossfade_fade_in_total - crossfade_fade_in_rem) << 8) /
- crossfade_fade_in_total;
- /* Bytes to fade */
- size_t fade_rem = MIN(length, crossfade_fade_in_rem);
-
- /* We _will_ fade this many bytes */
- crossfade_fade_in_rem -= fade_rem;
+ if (crossfade_fade_in_rem)
+ {
+ /* Fade factor for this packet */
+ int factor =
+ ((crossfade_fade_in_total - crossfade_fade_in_rem) << 8) /
+ crossfade_fade_in_total;
+ /* Bytes to fade */
+ size_t fade_rem = MIN(size, crossfade_fade_in_rem);
- if (crossfade_chunk)
- {
- /* Mix the data */
- size_t fade_total = fade_rem;
- fade_rem = crossfade_mix_fade(factor, fade_rem, buf,
- &crossfade_sample, &crossfade_chunk);
- length -= fade_total - fade_rem;
- buf += fade_total - fade_rem;
- if (!length)
- return;
- }
+ /* We _will_ fade this many bytes */
+ crossfade_fade_in_rem -= fade_rem;
- samples = fade_rem / 2;
- input_buf = (int16_t *)buf;
- /* Fade remaining samples in place */
- while (samples--)
- {
- int32_t sample = *input_buf;
- *input_buf++ = (sample * factor) >> 8;
- }
- }
-
- if (crossfade_chunk)
+ if (crossfade_index != INVALID_BUF_INDEX)
{
/* Mix the data */
- size_t mix_total = length;
- /* A factor of 256 means mix only, no fading */
- length = crossfade_mix_fade(256, length, buf,
- &crossfade_sample, &crossfade_chunk);
- buf += mix_total - length;
- if (!length)
+ size_t fade_total = fade_rem;
+ fade_rem = crossfade_mix_fade(factor, fade_rem, buf, &crossfade_index,
+ elapsed, offset);
+ fade_total -= fade_rem;
+ size -= fade_total;
+ buf += fade_total;
+
+ if (!size)
return;
}
- while (length > 0)
+ /* Fade remaining samples in place */
+ int samples = fade_rem / 4;
+ int16_t *input_buf = (int16_t *)buf;
+
+ while (samples--)
{
- COMMIT_IF_NEEDED;
- size_t pcmbuffer_index = pcmbuffer_pos + pcmbuffer_fillpos;
- size_t copy_n = MIN(length, pcmbuf_size - pcmbuffer_index);
- memcpy(&pcmbuffer[pcmbuffer_index], buf, copy_n);
- buf += copy_n;
- pcmbuffer_fillpos += copy_n;
- length -= copy_n;
+ int32_t left = input_buf[0];
+ int32_t right = input_buf[1];
+ *input_buf++ = left * factor >> 8;
+ *input_buf++ = right * factor >> 8;
}
}
+
+ if (crossfade_index != INVALID_BUF_INDEX)
+ {
+ /* Mix the data */
+ size_t mix_total = size;
+
+ /* A factor of 256 means mix only, no fading */
+ size = crossfade_mix_fade(256, size, buf, &crossfade_index,
+ elapsed, offset);
+ buf += mix_total - size;
+
+ if (!size)
+ return;
+ }
+
+ /* Data might remain in the fade buffer yet the fade-in has run its
+ course - finish it off as normal chunks */
+ while (size > 0)
+ {
+ size_t copy_n = size;
+ unsigned char *outbuf = get_write_buffer(&copy_n);
+ memcpy(outbuf, buf, copy_n);
+ commit_write_buffer(copy_n, elapsed, offset);
+ buf += copy_n;
+ size -= copy_n;
+ }
+
/* if no more fading-in to do, stop the crossfade */
- if (!(crossfade_fade_in_rem || crossfade_chunk))
- crossfade_active = false;
+#if 0
+ /* This way (the previous way) can cause a sudden volume jump if mixable
+ data is used up before the fade-in completes and that just sounds wrong
+ -- jethead71 */
+ if (!crossfade_fade_in_rem || crossfade_index == INVALID_BUF_INDEX)
+#endif
+ /* Let fade-in complete even if not fully overlapping the existing data */
+ if (!crossfade_fade_in_rem)
+ crossfade_status = CROSSFADE_INACTIVE;
}
static void pcmbuf_finish_crossfade_enable(void)
{
/* Copy the pending setting over now */
- crossfade_enabled = crossfade_enable_request;
+ crossfade_setting = crossfade_enable_request;
- pcmbuf_watermark = (crossfade_enabled && pcmbuf_size) ?
+ pcmbuf_watermark = (crossfade_setting != CROSSFADE_ENABLE_OFF && pcmbuf_size) ?
/* If crossfading, try to keep the buffer full other than 1 second */
(pcmbuf_size - BYTERATE) :
/* Otherwise, just use the default */
@@ -1011,20 +1138,20 @@ static void pcmbuf_finish_crossfade_enable(void)
bool pcmbuf_is_crossfade_active(void)
{
- return crossfade_active || crossfade_track_change_started;
+ return crossfade_status != CROSSFADE_INACTIVE;
}
-void pcmbuf_request_crossfade_enable(bool on_off)
+void pcmbuf_request_crossfade_enable(int setting)
{
/* Next setting to be used, not applied now */
- crossfade_enable_request = on_off;
+ crossfade_enable_request = setting;
}
bool pcmbuf_is_same_size(void)
{
- /* if pcmbuffer is NULL, then not set up yet even once so always */
- bool same_size = pcmbuffer ?
- (get_next_required_pcmbuf_size() == pcmbuf_size) : true;
+ /* if pcmbuf_buffer is NULL, then not set up yet even once so always */
+ bool same_size = pcmbuf_buffer ?
+ (get_next_required_pcmbuf_chunks() == pcmbuf_desc_count) : true;
/* no buffer change needed, so finish crossfade setup now */
if (same_size)
@@ -1037,49 +1164,29 @@ bool pcmbuf_is_same_size(void)
/** Debug menu, other metrics */
-/* Amount of bytes left in the buffer. */
+/* Amount of bytes left in the buffer, accounting for uncommitted bytes */
size_t pcmbuf_free(void)
{
- if (read_chunk != NULL)
- {
- void *read = (void *)read_chunk->addr;
- void *write = &pcmbuffer[pcmbuffer_pos + pcmbuffer_fillpos];
- if (read < write)
- return (size_t)(read - write) + pcmbuf_size;
- else
- return (size_t) (read - write);
- }
- return pcmbuf_size - pcmbuffer_fillpos;
+ return pcmbuf_size - pcmbuf_unplayed_bytes() - pcmbuf_bytes_waiting;
}
+/* Data bytes allocated for buffer */
size_t pcmbuf_get_bufsize(void)
{
return pcmbuf_size;
}
+/* Number of committed descriptors */
int pcmbuf_used_descs(void)
{
- struct chunkdesc *temp = read_chunk;
- unsigned int i = 0;
- while (temp) {
- temp = temp->link;
- i++;
- }
- return i;
+ return pcmbuf_unplayed_bytes() / PCMBUF_CHUNK_SIZE;
}
+/* Total number of descriptors allocated */
int pcmbuf_descs(void)
{
- return NUM_CHUNK_DESCS(pcmbuf_size);
-}
-
-#ifdef ROCKBOX_HAS_LOGF
-unsigned char *pcmbuf_get_meminfo(size_t *length)
-{
- *length = pcmbuf_bufend - pcmbuffer;
- return pcmbuffer;
+ return pcmbuf_desc_count;
}
-#endif
/** Fading and channel volume control */
@@ -1112,7 +1219,6 @@ static void pcmbuf_fade_tick(void)
{
/* Fade is complete */
tick_remove_task(pcmbuf_fade_tick);
-
if (fade_state == PCM_FADING_OUT)
{
/* Tell PCM to stop at its earliest convenience */
@@ -1177,19 +1283,15 @@ void pcmbuf_soft_mode(bool shhh)
bool pcmbuf_is_lowdata(void)
{
- if (!pcm_is_playing() || pcm_is_paused()
-#ifdef HAVE_CROSSFADE
- || pcmbuf_is_crossfade_active()
-#endif
- )
+ if (!audio_pcmbuf_may_play() || pcmbuf_is_crossfade_active())
return false;
#if MEMORYSIZE > 2
/* 1 seconds of buffer is low data */
- return LOW_DATA(4);
+ return pcmbuf_unplayed_bytes() < DATA_LEVEL(4);
#else
/* under watermark is low data */
- return (pcmbuf_unplayed_bytes < pcmbuf_watermark);
+ return pcmbuf_unplayed_bytes() < pcmbuf_watermark;
#endif
}
@@ -1198,8 +1300,15 @@ void pcmbuf_set_low_latency(bool state)
low_latency_mode = state;
}
-unsigned long pcmbuf_get_latency(void)
+/* Return the current position key value */
+unsigned int pcmbuf_get_position_key(void)
+{
+ return position_key;
+}
+
+/* Set position updates to be synchronous and immediate in addition to during
+ PCM frames - cancelled upon first codec insert or upon stopping */
+void pcmbuf_sync_position_update(void)
{
- return (pcmbuf_unplayed_bytes +
- mixer_channel_get_bytes_waiting(PCM_MIXER_CHAN_PLAYBACK)) * 1000 / BYTERATE;
+ pcmbuf_sync_position = true;
}