Index: apps/pcmbuf.c =================================================================== --- apps/pcmbuf.c (revision 30212) +++ apps/pcmbuf.c (working copy) @@ -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,23 +36,27 @@ #if (CONFIG_PLATFORM & PLATFORM_NATIVE) #include "cpu.h" #endif -#include #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 */ +/* 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 */ +#define PCMBUF_CHUNK_SIZE 8192u +#define PCMBUF_GUARD_SIZE 1024u -/* number of bytes played per second (sample rate * 2 channels * 2 bytes/sample) */ +/* 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 @@ -63,32 +68,45 @@ /* Structure we can use to queue pcm chunks in memory to be played * by the driver code. */ +enum chunk_flags +{ + CHUNK_END_OF_TRACK = 0x1, + CHUNK_HAS_POSITION = 0x2, +}; + +/* 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 flags; /* Flags indicating additional properties */ + uint8_t version; /* Who put the timestamps in */ + unsigned long elapsed; /* Elapsed time to use (if CHUNK_HAS_POSITION) */ + off_t offset; /* Offset to use (if CHUNK_HAS_POSITION) */ }; -#define NUM_CHUNK_DESCS(bufsize) \ - ((bufsize) / PCMBUF_MINAVG_CHUNK) +/* General PCM buffer data */ +#define INVALID_BUF_INDEX (~(size_t)0) -/* 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 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_version; -static struct chunkdesc *first_desc; +static size_t pcmbuf_bytes_waiting; -/* Gapless playback */ -static bool track_transition; +static size_t chunk_ridx; +static size_t chunk_widx; +static volatile size_t pcmbuf_bytes_written; +static volatile size_t pcmbuf_bytes_read; +static size_t pcmbuf_watermark; +static size_t last_chunksize; + +static bool low_latency_mode = false; + + /* Fade effect */ static unsigned int fade_vol = MIX_AMP_UNITY; @@ -97,184 +115,174 @@ #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; /* 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; +/* 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 */ +}; +#endif /* HAVE_CROSSFADE */ -static size_t pcmbuf_unplayed_bytes; -static size_t pcmbuf_watermark; - -static bool low_latency_mode = false; -static bool flush_pcmbuf = false; - +/* 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) +#define DATA_LEVEL(quarter_secs) (NATIVE_FREQUENCY * (quarter_secs)) #ifdef HAVE_CROSSFADE static void crossfade_start(void); -static void write_to_crossfade(size_t length); +static void write_to_crossfade(size_t size, unsigned long elapsed, + off_t offset); static void pcmbuf_finish_crossfade_enable(void); -#endif +#endif /* HAVE_CROSSFADE */ /* 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); extern void audio_pcmbuf_track_change(bool pcmbuf); extern bool audio_pcmbuf_may_play(void); /**************************************/ -/* define this to show detailed chunkdesc usage information on the sim console */ -/*#define DESC_DEBUG*/ +/* Return number of commited bytes in buffer */ +static FORCE_INLINE size_t pcmbuf_unplayed_bytes(void) +{ + return pcmbuf_bytes_written - pcmbuf_bytes_read; +} -#ifndef SIMULATOR -#undef DESC_DEBUG -#endif +/* Return the next PCM chunk in the PCM array given a byte index into the + array */ +static size_t index_next(size_t index) +{ + index = ALIGN_DOWN(index + PCMBUF_CHUNK_SIZE, PCMBUF_CHUNK_SIZE); -#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) + if (index >= pcmbuf_size) + index -= pcmbuf_size; -static bool show_desc(char *caller) + return index; +} + +/* 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 the chunk descriptors, stopping + when below the threshold */ +static void commit_chunks(size_t threshold) { - if (!pcmbuffer_fillpos) - return; + size_t index = chunk_widx; + size_t end_index = index + pcmbuf_bytes_waiting; - /* 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); - } + /* Copy all data that must wrap to the beginning of the buffer */ + if (end_index > pcmbuf_size) + memcpy(pcmbuf_buffer, pcmbuf_guardbuf, end_index - pcmbuf_size); - /* commit the chunk */ + struct chunkdesc *desc = index_chunkdesc(index); - 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; - - 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); - 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; + pcmbuf_bytes_waiting -= 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; + /* Make the next chunk current write chunk */ + 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->flags = 0; + desc->version = 0; - /* Update bytes counters */ - pcmbuf_unplayed_bytes += size; + /* Update bytes counters and make it visible to the callback - + all committed chunks count as a full chunk in the bytekeeping + no matter the actual data size */ + pcmbuf_bytes_written += PCMBUF_CHUNK_SIZE; + } + while (pcmbuf_bytes_waiting >= threshold); +} - pcmbuffer_pos += size; - if (pcmbuffer_pos >= pcmbuf_size) - pcmbuffer_pos -= pcmbuf_size; +/* If uncommitted data is above or equal to the threshold, split into chunks + and commit it - threshold should be > 0 */ +static FORCE_INLINE void commit_if_needed(size_t threshold) +{ + if (pcmbuf_bytes_waiting >= threshold) + commit_chunks(threshold); +} - pcmbuffer_fillpos = 0; - DISPLAY_DESC("commit_chunk"); +/* 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 */ + if (!(desc->flags & CHUNK_HAS_POSITION) || + desc->version != position_version) + { + desc->flags |= CHUNK_HAS_POSITION; + desc->elapsed = elapsed; + desc->offset = offset; + desc->version = position_version; + } } /* Set priority of the codec thread */ @@ -283,7 +291,8 @@ * 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% */ @@ -291,12 +300,13 @@ 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 @@ -312,37 +322,82 @@ #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) +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); +} + +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) +{ + commit_if_needed(COMMIT_CHUNKS); + + 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_track_change_started) + crossfade_start(); + + /* If crossfade has begun, put the new track samples in crossfade_buffer */ + if (crossfade_active && 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 (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)) - { + if (remaining < DATA_LEVEL(2)) crossfade_active = false; - } #endif } else /* !playing */ @@ -352,9 +407,9 @@ /* 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"); @@ -363,194 +418,198 @@ } } - return true; + void *buf = +#ifdef HAVE_CROSSFADE + crossfade_active ? crossfade_buffer : +#endif + get_write_buffer(&size); + + *count = size / 4; + return buf; } -/* Request space in the buffer for writing output samples */ -void *pcmbuf_request_buffer(int *count) +/* Handle new samples to the buffer */ +void pcmbuf_write_complete(int count, unsigned long elapsed, off_t offset) { + 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_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; - } + write_to_crossfade(size, elapsed, offset); } else #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]; - } - } + commit_write_buffer(size, elapsed, offset); } - /* PCM buffer not ready to receive new data yet */ - return NULL; -} +#if 0 + if (status != CHANNEL_PLAYING && remaining) + { + struct chunkdesc *desc = index_chunkdesc(chunk_ridx); -/* Handle new samples to the buffer */ -void pcmbuf_write_complete(int count) -{ - size_t length = (size_t)(unsigned int)count << 2; -#ifdef HAVE_CROSSFADE - if (crossfade_active) - write_to_crossfade(length); - else + if (desc->flags & CHUNK_HAS_POSITION) + audio_pcmbuf_position_callback(desc->elapsed, desc->offset); + } #endif - { - pcmbuffer_fillpos += length; - COMMIT_IF_NEEDED; - } } /** Init */ - -static inline void init_pcmbuffers(void) +static unsigned int get_next_required_pcmbuf_chunks(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"); -} + size_t seconds; -static size_t get_next_required_pcmbuf_size(void) -{ - size_t seconds = 1; +#if MEMORYSIZE > 2 + /* Buffer has to be at least 2s long. */ + seconds = 3; +#else + seconds = 1; +#endif #ifdef HAVE_CROSSFADE - if (crossfade_enable_request) + if (crossfade_enable_request != CROSSFADE_ENABLE_OFF) + { seconds += global_settings.crossfade_fade_out_delay + global_settings.crossfade_fade_out_duration; + } #endif -#if MEMORYSIZE > 2 - /* Buffer has to be at least 2s long. */ - seconds += 2; -#endif logf("pcmbuf len: %ld", (long)seconds); - return seconds * BYTERATE; + return (seconds * BYTERATE) / PCMBUF_CHUNK_SIZE; } -/* 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; - init_pcmbuffers(); + pcmbuf_buffer = (void *)pcmbuf_descriptors - + pcmbuf_size - PCMBUF_GUARD_SIZE; + pcmbuf_buffer = ALIGN_DOWN(pcmbuf_buffer, (uintptr_t)MEM_ALIGN_SIZE); + + pcmbuf_guardbuf = pcmbuf_buffer + pcmbuf_size; + bufstart = pcmbuf_buffer; + #ifdef HAVE_CROSSFADE + if (crossfade_enable_request != CROSSFADE_ENABLE_OFF) + { + bufstart -= CROSSFADE_BUFSIZE; + crossfade_buffer = bufstart; + } + pcmbuf_finish_crossfade_enable(); #else pcmbuf_watermark = PCMBUF_WATERMARK; #endif pcmbuf_play_stop(); - pcmbuf_soft_mode(false); - return pcmbuf_bufend - pcmbuffer; + return bufend - bufstart; } /** Track change */ -void pcmbuf_monitor_track_change(bool monitor) + +/* 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) { - pcm_play_lock(); - - if (last_chunksize != 0) + if (pcmbuf_unplayed_bytes() && 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; - } + index_chunkdesc_offs(index, offset)->flags |= CHUNK_END_OF_TRACK; } 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 flags and others in all descriptors */ +static void pcmbuf_cancel_track_change(unsigned int remove_flags) +{ + remove_flags |= CHUNK_END_OF_TRACK; + + size_t index = chunk_ridx; + + while (index != chunk_widx) + { + index_chunkdesc(index)->flags &= ~remove_flags; + 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(0); + pcm_play_unlock(); } +/* Commit any outstanding data */ +void pcmbuf_drain(void) +{ + commit_if_needed(COMMIT_ALL_DATA); + + /* Kick playback if things stopped or never got started */ + if (audio_pcmbuf_may_play()) + pcmbuf_play_start(); +} + bool pcmbuf_start_track_change(bool auto_skip) { bool crossfade = false; + #ifdef HAVE_CROSSFADE /* Determine whether this track change needs to crossfade */ - if(crossfade_enabled && !pcmbuf_is_crossfade_active()) + if (crossfade_setting != CROSSFADE_ENABLE_OFF && + !pcmbuf_is_crossfade_active()) { - switch(global_settings.crossfade) + switch (crossfade_setting) { - 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; + 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; } } -#endif +#endif /* HAVE_CROSSFADE */ + /* Update position version so buffers stamped to the outgoing track's + positions are restamped to the incoming track's positions when + crossfading */ + if (++position_version > UINT8_MAX) + position_version = 1; + if (!auto_skip || crossfade) /* manual skip or crossfade */ { @@ -559,29 +618,14 @@ else { logf(" manual track change"); } - pcm_play_lock(); - - /* Cancel any pending automatic gapless transition */ - pcmbuf_monitor_track_change(false); - - /* Can't do two crossfades at once and, no fade if pcm is off now */ - if ( -#ifdef HAVE_CROSSFADE - pcmbuf_is_crossfade_active() || -#endif - mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_STOPPED) + if (pcmbuf_unplayed_bytes() < DATA_LEVEL(2) || !crossfade + || low_latency_mode) { + /* Not enough data, not crossfading, can't do two crossfades at + once or pcm is off now (implying low data) - discard the old + data instead */ pcmbuf_play_stop(); - pcm_play_unlock(); - /* Notify playback that the track change starts now */ - return true; } - - /* 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 { @@ -591,15 +635,20 @@ crossfade_auto_skip = auto_skip; - crossfade_track_change_started = crossfade; + crossfade_track_change_started = true; + + trigger_cpu_boost(); + + /* Cancel any pending automatic gapless transition */ + pcm_play_lock(); + pcmbuf_cancel_track_change(auto_skip ? 0 : CHUNK_HAS_POSITION); + pcm_play_unlock(); + + /* Track change happens when new track is written if automatic */ + return !auto_skip; } #endif - pcm_play_unlock(); - /* 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; } @@ -618,108 +667,79 @@ /** 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) { + /* Get the chunk that just completed */ + size_t index = chunk_ridx; + struct chunkdesc *desc = index_chunkdesc(index); + bool endoftrack = false; + + /* Process the chunk that just finished */ + if (last_chunksize != 0) { - struct chunkdesc *pcmbuf_current = read_chunk; - /* Take the finished chunk out of circulation */ - read_chunk = pcmbuf_current->link; + /* If last chunk in the track, notify of track change */ + endoftrack = desc->flags & CHUNK_END_OF_TRACK; - /* 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; - 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); + desc = index_chunkdesc(index); + pcmbuf_bytes_read += PCMBUF_CHUNK_SIZE; } + /* Process the new one */ + if (pcmbuf_unplayed_bytes()) { - /* Commit last samples at end of playlist */ - if (pcmbuffer_fillpos && !read_chunk) + /* Send the new chunk to the mixer */ + last_chunksize = PCMBUF_CHUNK_SIZE; + *start = index_buffer(index); + *size = desc->size; + + if (desc->flags & CHUNK_HAS_POSITION) { - logf("pcmbuf_pcm_callback: commit last samples"); - commit_chunk(false); + /* Positioning chunk - notify playback */ + audio_pcmbuf_position_callback(desc->elapsed, desc->offset); } } - { - /* Send the new chunk to the DMA */ - if(read_chunk) - { - last_chunksize = read_chunk->size; - pcmbuf_unplayed_bytes -= last_chunksize; - *size = last_chunksize; - *start = read_chunk->addr; - } - else - { - /* No more chunks */ - logf("pcmbuf_pcm_callback: no more chunks"); - last_chunksize = 0; - *size = 0; - *start = NULL; - } - } - DISPLAY_DESC("callback"); + if (endoftrack) + audio_pcmbuf_track_change(true); } /* 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) + pcmbuf_unplayed_bytes()) { - logf("pcmbuf_play_start"); - last_chunksize = read_chunk->size; - pcmbuf_unplayed_bytes -= last_chunksize; - mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, - pcmbuf_pcm_callback, NULL, 0); + last_chunksize = 0; + mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, pcmbuf_pcm_callback, + NULL, 0); } } +/* Stop channel, empty and reset buffer */ void pcmbuf_play_stop(void) { logf("pcmbuf_play_stop"); 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; - } + position_version = 1; + chunk_ridx = chunk_widx = 0; + struct chunkdesc *desc = index_chunkdesc(0); + desc->flags = 0; + desc->version = 0; + + pcmbuf_bytes_written = pcmbuf_bytes_read = 0; + pcmbuf_bytes_waiting = 0; + last_chunksize = 0; - pcmbuffer_pos = 0; - pcmbuffer_fillpos = 0; #ifdef HAVE_CROSSFADE crossfade_track_change_started = false; crossfade_active = false; #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 */ @@ -739,74 +759,153 @@ /** 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; - while (length) + if (index == INVALID_BUF_INDEX) + return size; + + const int16_t *input_buf = buf; + int16_t *output_buf = (int16_t *)index_buffer(index); + + 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->flags &= ~CHUNK_HAS_POSITION; + 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; } + + *output_buf++ = left; + *output_buf++ = right; + + rem -= 4; } + while (rem); - length -= 4; /* 2 samples, each 16 bit -> 4 bytes */ - /* 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; } @@ -814,184 +913,194 @@ * 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; + logf("crossfade_start"); crossfade_track_change_started = false; + + commit_if_needed(COMMIT_ALL_DATA); + + pcm_play_lock(); + + /* Initialize the crossfade buffer size to all of the buffered data that + * has not yet been sent to the DMA */ + size_t unplayed = pcmbuf_unplayed_bytes(); + /* Reject crossfade if less than .5s of data */ - if (LOW_DATA(2)) { + if (unplayed < DATA_LEVEL(2)) + { logf("crossfade rejected"); - pcmbuf_play_stop(); - return ; + + if (crossfade_auto_skip) + pcmbuf_monitor_track_change(true); + + pcm_play_unlock(); + return; } - logf("crossfade_start"); - commit_chunk(false); + /* Fading will happen */ crossfade_active = true; - /* 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 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_out_need = fade_out_delay + fade_out_rem; + size_t buffer_rem = crossfade_find_buftail(unplayed, fade_out_need); - crossfade_need = fade_out_delay + fade_out_rem; - if (crossfade_rem > crossfade_need) + pcm_play_unlock(); + + if (buffer_rem < fade_out_need) { - 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 */ + /* Existing buffers are short */ + size_t fade_out_short = fade_out_need - buffer_rem; + + if (fade_out_rem >= fade_out_short) { - crossfade_sample = find_chunk(crossfade_rem - crossfade_need, - &crossfade_chunk) / 2; - crossfade_rem = crossfade_need; + /* Truncate fade-out duration */ + fade_out_rem -= fade_out_short; } else - /* Manual skips occur immediately, but give time to process */ { - crossfade_rem -= crossfade_chunk->size; - crossfade_chunk = crossfade_chunk->link; - } - } - /* Truncate fade out duration if necessary. */ - if (crossfade_rem < crossfade_need) - { - size_t crossfade_short = crossfade_need - crossfade_rem; - if (fade_out_rem >= crossfade_short) - fade_out_rem -= crossfade_short; - else - { - fade_out_delay -= crossfade_short - fade_out_rem; + /* Truncate fade-out and fade-out delay */ + fade_out_delay = fade_out_rem; fade_out_rem = 0; } } - crossfade_rem -= fade_out_delay + fade_out_rem; /* Completely process the crossfade fade-out effect with current PCM buffer */ if (!crossfade_mixmode) { /* 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); } /* Initialize fade-in counters */ crossfade_fade_in_total = global_settings.crossfade_fade_in_duration * BYTERATE; crossfade_fade_in_rem = crossfade_fade_in_total; - fade_in_delay = global_settings.crossfade_fade_in_delay * BYTERATE; + pcm_play_lock(); - /* 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; + /* 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 - forego fade-in delay + on manual skip */ + unplayed = pcmbuf_unplayed_bytes(); + + if (crossfade_auto_skip) + unplayed += global_settings.crossfade_fade_in_delay * BYTERATE; + + buffer_rem = crossfade_find_buftail(unplayed, fade_out_need); + + if (crossfade_auto_skip) + pcmbuf_monitor_track_change_ex(crossfade_index, 0); + + pcm_play_unlock(); + 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) + if (size) { - char *buf = fadebuf; + unsigned char *buf = crossfade_buffer; + if (crossfade_fade_in_rem) { - size_t samples; - int16_t *input_buf; - /* 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); + size_t fade_rem = MIN(size, crossfade_fade_in_rem); /* We _will_ fade this many bytes */ crossfade_fade_in_rem -= fade_rem; - if (crossfade_chunk) + if (crossfade_index != INVALID_BUF_INDEX) { /* 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) + 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; } - samples = fade_rem / 2; - input_buf = (int16_t *)buf; /* Fade remaining samples in place */ + int samples = fade_rem / 4; + int16_t *input_buf = (int16_t *)buf; + while (samples--) { - int32_t sample = *input_buf; - *input_buf++ = (sample * factor) >> 8; + int32_t left = input_buf[0]; + int32_t right = input_buf[1]; + *input_buf++ = left * factor >> 8; + *input_buf++ = right * factor >> 8; } } - if (crossfade_chunk) + if (crossfade_index != INVALID_BUF_INDEX) { /* Mix the data */ - size_t mix_total = length; + size_t mix_total = size; + /* 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 = crossfade_mix_fade(256, size, buf, &crossfade_index, + elapsed, offset); + buf += mix_total - size; + + if (!size) return; } - while (length > 0) + /* Data might remain in the fade buffer yet the fade-in has run its + course - finish it off as normal chunks */ + while (size > 0) { - 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); + size_t copy_n = size; + unsigned char *outbuf = get_write_buffer(©_n); + memcpy(outbuf, buf, copy_n); + commit_write_buffer(copy_n, elapsed, offset); buf += copy_n; - pcmbuffer_fillpos += copy_n; - length -= copy_n; + size -= copy_n; } } + /* if no more fading-in to do, stop the crossfade */ - if (!(crossfade_fade_in_rem || crossfade_chunk)) + if (!crossfade_fade_in_rem || crossfade_index == INVALID_BUF_INDEX) crossfade_active = false; } 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 */ @@ -1003,17 +1112,17 @@ return crossfade_active || crossfade_track_change_started; } -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) @@ -1026,51 +1135,37 @@ /** 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; + unsigned int rchunk = chunk_ridx / PCMBUF_CHUNK_SIZE; + unsigned int wchunk = chunk_widx / PCMBUF_CHUNK_SIZE; + + if (rchunk > wchunk) + wchunk += pcmbuf_desc_count; + + return wchunk - rchunk; } +/* Total number of descriptors allocated */ int pcmbuf_descs(void) { - return NUM_CHUNK_DESCS(pcmbuf_size); + return pcmbuf_desc_count; } -#ifdef ROCKBOX_HAS_LOGF -unsigned char *pcmbuf_get_meminfo(size_t *length) -{ - *length = pcmbuf_bufend - pcmbuffer; - return pcmbuffer; -} -#endif - /** Fading and channel volume control */ /* Sync the channel amplitude to all states */ @@ -1144,19 +1239,15 @@ 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 } @@ -1164,9 +1255,3 @@ { low_latency_mode = state; } - -unsigned long pcmbuf_get_latency(void) -{ - return (pcmbuf_unplayed_bytes + - mixer_channel_get_bytes_waiting(PCM_MIXER_CHAN_PLAYBACK)) * 1000 / BYTERATE; -} Index: apps/pcmbuf.h =================================================================== --- apps/pcmbuf.h (revision 30212) +++ apps/pcmbuf.h (working copy) @@ -21,9 +21,11 @@ #ifndef PCMBUF_H #define PCMBUF_H +#include + /* Commit PCM data */ void *pcmbuf_request_buffer(int *count); -void pcmbuf_write_complete(int count); +void pcmbuf_write_complete(int count, unsigned long elapsed, off_t offset); /* Init */ size_t pcmbuf_init(unsigned char *bufend); @@ -34,19 +36,20 @@ void pcmbuf_pause(bool pause); void pcmbuf_monitor_track_change(bool monitor); bool pcmbuf_start_track_change(bool manual_skip); +void pcmbuf_drain(void); /* Crossfade */ #ifdef HAVE_CROSSFADE bool pcmbuf_is_crossfade_active(void); -void pcmbuf_request_crossfade_enable(bool on_off); +void pcmbuf_request_crossfade_enable(int setting); bool pcmbuf_is_same_size(void); #else /* Dummy functions with sensible returns */ -static inline bool pcmbuf_is_crossfade_active(void) +static FORCE_INLINE bool pcmbuf_is_crossfade_active(void) { return false; } -static inline void pcmbuf_request_crossfade_enable(bool on_off) +static FORCE_INLINE void pcmbuf_request_crossfade_enable(bool on_off) { return; (void)on_off; } -static inline bool pcmbuf_is_same_size(void) +static FORCE_INLINE bool pcmbuf_is_same_size(void) { return true; } #endif @@ -59,15 +62,11 @@ size_t pcmbuf_get_bufsize(void); int pcmbuf_descs(void); int pcmbuf_used_descs(void); -#ifdef ROCKBOX_HAS_LOGF -unsigned char *pcmbuf_get_meminfo(size_t *length); -#endif /* Misc */ void pcmbuf_fade(bool fade, bool in); void pcmbuf_soft_mode(bool shhh); bool pcmbuf_is_lowdata(void); void pcmbuf_set_low_latency(bool state); -unsigned long pcmbuf_get_latency(void); #endif Index: apps/codec_thread.c =================================================================== --- apps/codec_thread.c (revision 30212) +++ apps/codec_thread.c (working copy) @@ -77,9 +77,10 @@ static int codec_type = AFMT_UNKNOWN; /* Codec type (C,A-) */ /* Private interfaces to main playback control */ -extern void audio_codec_update_elapsed(unsigned long value); -extern void audio_codec_update_offset(size_t value); -extern void audio_queue_post(long id, intptr_t data); +extern void audio_codec_update_elapsed(unsigned long elapsed); +extern void audio_codec_update_offset(size_t offset); +extern void audio_codec_complete(int status); +extern void audio_codec_seek_complete(void); extern struct codec_api ci; /* from codecs.c */ /* Codec thread */ @@ -251,7 +252,7 @@ if (out_count <= 0) return; - pcmbuf_write_complete(out_count); + pcmbuf_write_complete(out_count, ci.id3->elapsed, ci.id3->offset); count -= inp_count; } @@ -335,8 +336,7 @@ dsp_configure(ci.dsp, DSP_FLUSH, 0); /* Post notification to audio thread */ - LOGFQUEUE("audio > Q_AUDIO_CODEC_SEEK_COMPLETE"); - audio_queue_post(Q_AUDIO_CODEC_SEEK_COMPLETE, 0); + audio_codec_seek_complete(); /* Wait for urgent or go message */ do @@ -521,8 +521,7 @@ /* Notify audio that we're done for better or worse - advise of the status */ - LOGFQUEUE("codec > audio Q_AUDIO_CODEC_COMPLETE: %d", status); - audio_queue_post(Q_AUDIO_CODEC_COMPLETE, status); + audio_codec_complete(status); } } Index: apps/playback.c =================================================================== --- apps/playback.c (revision 30212) +++ apps/playback.c (working copy) @@ -331,7 +331,6 @@ static int codec_skip_status; static bool codec_seeking = false; /* Codec seeking ack expected? */ - /* Event queues */ static struct event_queue audio_queue SHAREDBSS_ATTR; @@ -358,9 +357,7 @@ /**************************************/ /** --- audio_queue helpers --- **/ - -/* codec thread needs access */ -void audio_queue_post(long id, intptr_t data) +static void audio_queue_post(long id, intptr_t data) { queue_post(&audio_queue, id, data); } @@ -796,14 +793,10 @@ aids viewing and the summation of certain variables should add up to the location of others. */ { - size_t pcmbufsize; - const unsigned char *pcmbuf = pcmbuf_get_meminfo(&pcmbufsize); logf("fbuf: %08X", (unsigned)filebuf); logf("fbufe: %08X", (unsigned)(filebuf + filebuflen)); logf("sbuf: %08X", (unsigned)audio_scratch_memory); logf("sbufe: %08X", (unsigned)(audio_scratch_memory + allocsize)); - logf("pcmb: %08X", (unsigned)pcmbuf); - logf("pcmbe: %08X", (unsigned)(pcmbuf + pcmbufsize)); } #endif @@ -969,7 +962,8 @@ /* Announce the end of playing the current track */ static void audio_playlist_track_finish(void) { - struct mp3entry *id3 = valid_mp3entry(id3_get(PLAYING_ID3)); + struct mp3entry *ply_id3 = id3_get(PLAYING_ID3); + struct mp3entry *id3 = valid_mp3entry(ply_id3); playlist_update_resume_info(filling == STATE_ENDED ? NULL : id3); @@ -1005,29 +999,16 @@ /* Bring the user current mp3entry up to date and set a new offset for the buffered metadata */ -static void playing_id3_sync(struct track_info *user_info, size_t offset) +static void playing_id3_sync(struct track_info *user_info, off_t offset) { id3_mutex_lock(); struct mp3entry *id3 = bufgetid3(user_info->id3_hid); - if (offset == (size_t)-1) - { - struct mp3entry *ply_id3 = id3_get(PLAYING_ID3); - size_t play_offset = ply_id3->offset; - long play_elapsed = ply_id3->elapsed; - id3_write(PLAYING_ID3, id3); - ply_id3->offset = play_offset; - ply_id3->elapsed = play_elapsed; - offset = 0; - } - else - { - id3_write(PLAYING_ID3, id3); - } + id3_write(PLAYING_ID3, id3); if (id3) - id3->offset = offset; + id3->offset = MAX(0, offset); id3_mutex_unlock(); } @@ -1084,13 +1065,6 @@ return retval; } -/* Clear the PCM on a manual skip */ -static void audio_clear_paused_pcm(void) -{ - if (play_status == PLAY_PAUSED && !pcmbuf_is_crossfade_active()) - pcmbuf_play_stop(); -} - /* End the ff/rw mode */ static void audio_ff_rewind_end(void) { @@ -2045,7 +2019,7 @@ /* Called to make an outstanding track skip the current track and to send the transition events */ -static void audio_finalise_track_change(bool delayed) +static void audio_finalise_track_change(void) { switch (skip_pending) { @@ -2099,15 +2073,6 @@ id3_write(PLAYING_ID3, track_id3); - if (delayed) - { - /* Delayed skip where codec is ahead of user's current track */ - struct mp3entry *ci_id3 = id3_get(CODEC_ID3); - struct mp3entry *ply_id3 = id3_get(PLAYING_ID3); - ply_id3->elapsed = ci_id3->elapsed; - ply_id3->offset = ci_id3->offset; - } - /* The skip is technically over */ skip_pending = TRACK_SKIP_NONE; @@ -2133,15 +2098,12 @@ if (finalised) { /* pcmbuf says that the transition happens now - complete it */ - audio_finalise_track_change(false); + audio_finalise_track_change(); if (play_status == PLAY_STOPPED) return; /* Stopped us */ } - if (!auto_skip) - audio_clear_paused_pcm(); - if (trackstat >= LOAD_TRACK_OK) { struct track_info *info = track_list_current(0); @@ -2203,19 +2165,14 @@ codec_skip_pending = false; -#ifdef AB_REPEAT_ENABLE - if (status >= 0) - { - /* Normal automatic skip */ - ab_end_of_track_report(); - } -#endif - int trackstat = LOAD_TRACK_OK; automatic_skip = true; skip_pending = TRACK_SKIP_AUTO; + /* Let PCM play any remaining data */ + pcmbuf_drain(); + /* Does this track have an entry allocated? */ struct track_info *info = track_list_advance_current(1); @@ -2298,7 +2255,7 @@ static void audio_on_track_changed(void) { /* Finish whatever is pending so that the WPS is in sync */ - audio_finalise_track_change(true); + audio_finalise_track_change(); if (codec_skip_pending) { @@ -2350,7 +2307,6 @@ /* Indicate manual track change */ pcmbuf_start_track_change(false); - audio_clear_paused_pcm(); wipe_track_metadata(true); } @@ -3104,75 +3060,55 @@ /** -- Codec callbacks -- **/ -/* Update elapsed times with latency-adjusted values */ -void audio_codec_update_elapsed(unsigned long value) +/* Update elapsed time for next PCM insert */ +void audio_codec_update_elapsed(unsigned long elapsed) { #ifdef AB_REPEAT_ENABLE - ab_position_report(value); + ab_position_report(elapsed); #endif - - unsigned long latency = pcmbuf_get_latency(); - - if (LIKELY(value >= latency)) - { - unsigned long elapsed = value - latency; - - if (elapsed > value || elapsed < value - 2) - value = elapsed; - } - else - { - value = 0; - } - - /* Track codec: used later when updating the playing at the user - transition */ - id3_get(CODEC_ID3)->elapsed = value; - - /* If a skip is pending, the PCM buffer is updating the time on the - previous song */ - if (LIKELY(skip_pending == TRACK_SKIP_NONE)) - id3_get(PLAYING_ID3)->elapsed = value; + /* Save in codec's id3 where it is used at next pcm insert */ + id3_get(CODEC_ID3)->elapsed = elapsed; } -/* Update offsets with latency-adjusted values */ -void audio_codec_update_offset(size_t value) +/* Update offset for next PCM insert */ +void audio_codec_update_offset(size_t offset) { - struct mp3entry *ci_id3 = id3_get(CODEC_ID3); - unsigned long latency = pcmbuf_get_latency() * ci_id3->bitrate / 8; + /* Save in codec's id3 where it is used at next pcm insert */ + id3_get(CODEC_ID3)->offset = offset; +} - if (LIKELY(value >= latency)) +/* Codec has finished running */ +void audio_codec_complete(int status) +{ +#ifdef AB_REPEAT_ENABLE + if (status >= CODEC_OK) { - value -= latency; + /* Normal automatic skip */ + ab_end_of_track_report(); } - else - { - value = 0; - } +#endif - /* Track codec: used later when updating the playing id3 at the user - transition */ - ci_id3->offset = value; + LOGFQUEUE("codec > audio Q_AUDIO_CODEC_COMPLETE: %d", status); + audio_queue_post(Q_AUDIO_CODEC_COMPLETE, status); +} - /* If a skip is pending, the PCM buffer is updating the time on the - previous song */ - if (LIKELY(skip_pending == TRACK_SKIP_NONE)) - id3_get(PLAYING_ID3)->offset = value; +/* Codec has finished seeking */ +void audio_codec_seek_complete(void) +{ + LOGFQUEUE("codec > audio Q_AUDIO_CODEC_SEEK_COMPLETE"); + audio_queue_post(Q_AUDIO_CODEC_SEEK_COMPLETE, 0); } /** --- Pcmbuf callbacks --- **/ -/* Between the codec and PCM track change, we need to keep updating the - * "elapsed" value of the previous (to the codec, but current to the - * user/PCM/WPS) track, so that the progressbar reaches the end. */ -void audio_pcmbuf_position_callback(unsigned int time) +/* Update the elapsed and offset from the information cached during the + PCM buffer insert */ +void audio_pcmbuf_position_callback(unsigned long elapsed, off_t offset) { struct mp3entry *id3 = id3_get(PLAYING_ID3); - - time += id3->elapsed; - - id3->elapsed = MIN(time, id3->length); + id3->elapsed = elapsed; + id3->offset = offset; } /* Post message from pcmbuf that the end of the previous track has just Index: apps/codecs/vorbis.c =================================================================== --- apps/codecs/vorbis.c (revision 30212) +++ apps/codecs/vorbis.c (working copy) @@ -196,6 +196,9 @@ ci->set_elapsed(ov_time_tell(&vf)); ci->set_offset(ov_raw_tell(&vf)); } + else { + ci->set_elapsed(0); + } previous_section = -1; eof = 0; Index: apps/codecs/flac.c =================================================================== --- apps/codecs/flac.c (revision 30212) +++ apps/codecs/flac.c (working copy) @@ -460,7 +460,9 @@ codec_set_replaygain(ci->id3); flac_seek_offset(&fc, samplesdone); - samplesdone=0; + samplesdone=fc.samplenumber+fc.blocksize; + elapsedtime=(samplesdone*10)/(ci->id3->frequency/100); + ci->set_elapsed(elapsedtime); /* The main decoding loop */ frame=0; Index: apps/codecs/aiff.c =================================================================== --- apps/codecs/aiff.c (revision 30212) +++ apps/codecs/aiff.c (working copy) @@ -288,6 +288,8 @@ bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; Index: apps/codecs/ape.c =================================================================== --- apps/codecs/ape.c (revision 30212) +++ apps/codecs/ape.c (working copy) @@ -220,6 +220,9 @@ firstbyte = 3; /* Take account of the little-endian 32-bit byte ordering */ } + elapsedtime = (samplesdone*10)/(ape_ctx.samplerate/100); + ci->set_elapsed(elapsedtime); + /* Initialise the buffer */ inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); Index: apps/codecs/au.c =================================================================== --- apps/codecs/au.c (revision 30212) +++ apps/codecs/au.c (working copy) @@ -253,6 +253,8 @@ bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; Index: apps/codecs/adx.c =================================================================== --- apps/codecs/adx.c (revision 30212) +++ apps/codecs/adx.c (working copy) @@ -209,8 +209,8 @@ /* get in position */ ci->seek_buffer(bufoff); + ci->set_elapsed(0); - /* setup pcm buffer format */ ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); if (channels == 2) { @@ -276,6 +276,11 @@ loop_count++; } ci->seek_buffer(bufoff); + + ci->set_elapsed( + ((end_adr-start_adr)*loop_count + bufoff-chanstart)* + 1000LL/avgbytespersec); + ci->seek_complete(); } Index: apps/codecs/vox.c =================================================================== --- apps/codecs/vox.c (revision 30212) +++ apps/codecs/vox.c (working copy) @@ -141,6 +141,8 @@ bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; Index: apps/codecs/mpa.c =================================================================== --- apps/codecs/mpa.c (revision 30212) +++ apps/codecs/mpa.c (working copy) @@ -144,6 +144,7 @@ { unsigned long offset = id3->offset > id3->first_frame_offset ? id3->offset - id3->first_frame_offset : 0; + unsigned long elapsed = id3->elapsed; if ( id3->vbr ) { if ( id3->has_toc ) { @@ -172,27 +173,28 @@ /* set time for this percent (divide before multiply to prevent overflow on long files. loss of precision is negligible on short files) */ - id3->elapsed = i * (id3->length / 100); + elapsed = i * (id3->length / 100); /* calculate remainder time */ plen = (nextpos - relpos) * (id3->filesize / 256); - id3->elapsed += (((remainder * 100) / plen) * - (id3->length / 10000)); + elapsed += (((remainder * 100) / plen) * (id3->length / 10000)); } else { /* no TOC exists. set a rough estimate using average bitrate */ int tpk = id3->length / ((id3->filesize - id3->first_frame_offset - id3->id3v1len) / 1024); - id3->elapsed = offset / 1024 * tpk; + elapsed = offset / 1024 * tpk; } } else { /* constant bitrate, use exact calculation */ if (id3->bitrate != 0) - id3->elapsed = offset / (id3->bitrate / 8); + elapsed = offset / (id3->bitrate / 8); } + + ci->set_elapsed(elapsed); } #ifdef MPA_SYNTH_ON_COP Index: apps/codecs/mpc.c =================================================================== --- apps/codecs/mpc.c (revision 30212) +++ apps/codecs/mpc.c (working copy) @@ -123,6 +123,8 @@ codec_set_replaygain(ci->id3); /* Resume to saved sample offset. */ + elapsed_time = 0; + if (samplesdone > 0) { if (mpc_demux_seek_sample(demux, samplesdone) == MPC_STATUS_OK) @@ -136,6 +138,8 @@ } } + ci->set_elapsed(elapsed_time); + /* This is the decoding loop. */ do { Index: apps/codecs/aac.c =================================================================== --- apps/codecs/aac.c (revision 30212) +++ apps/codecs/aac.c (working copy) @@ -134,8 +134,6 @@ if (m4a_seek_raw(&demux_res, &input_stream, file_offset, &sound_samples_done, (int*) &i)) { sound_samples_done *= sbr_fac; - elapsed_time = (sound_samples_done * 10) / (ci->id3->frequency / 100); - ci->set_elapsed(elapsed_time); } else { sound_samples_done = 0; } @@ -143,6 +141,9 @@ } else { sound_samples_done = 0; } + + elapsed_time = (sound_samples_done * 10) / (ci->id3->frequency / 100); + ci->set_elapsed(elapsed_time); if (i == 0) { Index: apps/codecs/speex.c =================================================================== --- apps/codecs/speex.c (revision 30212) +++ apps/codecs/speex.c (working copy) @@ -417,6 +417,7 @@ } ci->seek_buffer(0); + ci->set_elapsed(0); stereo = speex_stereo_state_init(); spx_ogg_sync_init(&oy); Index: apps/codecs/spc.c =================================================================== --- apps/codecs/spc.c (revision 30212) +++ apps/codecs/spc.c (working copy) @@ -560,6 +560,8 @@ return CODEC_ERROR; DEBUGF("SPC: read size = 0x%lx\n",(unsigned long)buffersize); + ci->set_elapsed(0); + do { if (load_spc_buffer(buffer, buffersize)) { Index: apps/codecs/tta.c =================================================================== --- apps/codecs/tta.c (revision 30212) +++ apps/codecs/tta.c (working copy) @@ -90,6 +90,8 @@ decodedsamples = new_pos; } + ci->set_elapsed((uint64_t)info.LENGTH * 1000 * decodedsamples / info.DATALENGTH); + while (!endofstream) { enum codec_command_action action = ci->get_command(¶m); Index: apps/codecs/alac.c =================================================================== --- apps/codecs/alac.c (revision 30212) +++ apps/codecs/alac.c (working copy) @@ -97,6 +97,8 @@ } } + ci->set_elapsed(elapsedtime); + /* The main decoding loop */ while (i < demux_res.num_sample_byte_sizes) { enum codec_command_action action = ci->get_command(¶m); Index: apps/codecs/wmapro.c =================================================================== --- apps/codecs/wmapro.c (revision 30212) +++ apps/codecs/wmapro.c (working copy) @@ -79,6 +79,7 @@ ci->seek_buffer(ci->id3->first_frame_offset); elapsedtime = 0; + ci->set_elapsed(0); /* The main decoding loop */ Index: apps/codecs/wav64.c =================================================================== --- apps/codecs/wav64.c (revision 30212) +++ apps/codecs/wav64.c (working copy) @@ -381,6 +381,8 @@ bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; Index: apps/codecs/wmavoice.c =================================================================== --- apps/codecs/wmavoice.c (revision 30212) +++ apps/codecs/wmavoice.c (working copy) @@ -109,6 +109,8 @@ ci->seek_buffer(ci->id3->first_frame_offset); elapsedtime = 0; + ci->set_elapsed(0); + resume_offset = 0; /* The main decoding loop */ Index: apps/codecs/mod.c =================================================================== --- apps/codecs/mod.c (revision 30212) +++ apps/codecs/mod.c (working copy) @@ -1333,12 +1333,11 @@ /* New time is ready in param */ modplayer.patterntableposition = param/1000; modplayer.currentline = 0; - ci->set_elapsed(modplayer.patterntableposition*1000+500); ci->seek_complete(); } if(old_patterntableposition != modplayer.patterntableposition) { - ci->set_elapsed(modplayer.patterntableposition*1000+500); + ci->set_elapsed(modplayer.patterntableposition*1000); old_patterntableposition=modplayer.patterntableposition; } Index: apps/codecs/sid.c =================================================================== --- apps/codecs/sid.c (revision 30212) +++ apps/codecs/sid.c (working copy) @@ -1299,8 +1299,8 @@ nSamplesToRender = 0; /* Start the rendering from scratch */ /* Set the elapsed time to the current subsong (in seconds) */ + ci->set_elapsed(subSong*1000); ci->seek_complete(); - ci->set_elapsed(subSong*1000); } nSamplesRendered = 0; Index: apps/codecs/shorten.c =================================================================== --- apps/codecs/shorten.c (revision 30212) +++ apps/codecs/shorten.c (working copy) @@ -99,6 +99,8 @@ sc.bitindex = sc.gb.index - 8*consumed; seek_start: + ci->set_elapsed(0); + /* The main decoding loop */ ci->memset(&decoded0, 0, sizeof(int32_t)*MAX_DECODE_SIZE); ci->memset(&decoded1, 0, sizeof(int32_t)*MAX_DECODE_SIZE); @@ -118,7 +120,6 @@ if (param == 0 && ci->seek_buffer(sc.header_bits/8 + ci->id3->first_frame_offset)) { sc.bitindex = sc.header_bits - 8*(sc.header_bits/8); - ci->set_elapsed(0); ci->seek_complete(); goto seek_start; } Index: apps/codecs/wma.c =================================================================== --- apps/codecs/wma.c (revision 30212) +++ apps/codecs/wma.c (working copy) @@ -84,7 +84,6 @@ % wfx.packet_size; ci->seek_buffer(resume_offset - packet_offset); elapsedtime = asf_get_timestamp(&i); - ci->set_elapsed(elapsedtime); } else { @@ -93,6 +92,8 @@ elapsedtime = 0; } + ci->set_elapsed(elapsedtime); + resume_offset = 0; ci->configure(DSP_SWITCH_FREQUENCY, wfx.rate); ci->configure(DSP_SET_STEREO_MODE, wfx.channels == 1 ? Index: apps/codecs/a52_rm.c =================================================================== --- apps/codecs/a52_rm.c (revision 30212) +++ apps/codecs/a52_rm.c (working copy) @@ -178,6 +178,7 @@ } else { /* Seek to the first packet */ + ci->set_elapsed(0); ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE ); } Index: apps/codecs/smaf.c =================================================================== --- apps/codecs/smaf.c (revision 30212) +++ apps/codecs/smaf.c (working copy) @@ -429,6 +429,8 @@ bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; Index: apps/codecs/a52.c =================================================================== --- apps/codecs/a52.c (revision 30212) +++ apps/codecs/a52.c (working copy) @@ -158,7 +158,7 @@ } else { ci->seek_buffer(ci->id3->first_frame_offset); - samplesdone = 0; + ci->set_elapsed(0); } while (1) { Index: apps/codecs/cook.c =================================================================== --- apps/codecs/cook.c (revision 30212) +++ apps/codecs/cook.c (working copy) @@ -105,8 +105,10 @@ param = (int)resume_offset * ((sps * 8 * 1000)/rmctx.bit_rate); action = CODEC_ACTION_SEEK_TIME; } + else { + ci->set_elapsed(0); + } - ci->set_elapsed(0); ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE); /* The main decoder loop */ Index: apps/codecs/wavpack.c =================================================================== --- apps/codecs/wavpack.c (revision 30212) +++ apps/codecs/wavpack.c (working copy) @@ -75,7 +75,7 @@ ci->configure(DSP_SET_STEREO_MODE, nchans == 2 ? STEREO_INTERLEAVED : STEREO_MONO); sr_100 = ci->id3->frequency / 100; - ci->set_elapsed (0); + ci->set_elapsed (WavpackGetSampleIndex (wpc) / sr_100 * 10); /* The main decoder loop */ Index: apps/codecs/wav.c =================================================================== --- apps/codecs/wav.c (revision 30212) +++ apps/codecs/wav.c (working copy) @@ -378,6 +378,8 @@ bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; Index: firmware/export/system.h =================================================================== --- firmware/export/system.h (revision 30212) +++ firmware/export/system.h (working copy) @@ -382,12 +382,16 @@ #if defined(CPU_ARM) /* Use ARMs cache alignment. */ #define MEM_ALIGN_ATTR CACHEALIGN_ATTR + #define MEM_ALIGN_SIZE CACHEALIGN_SIZE #elif defined(CPU_COLDFIRE) /* Use fixed alignment of 16 bytes. Speed up only for 'movem' in DRAM. */ #define MEM_ALIGN_ATTR __attribute__((aligned(16))) + #define MEM_ALIGN_SIZE 16 #else /* Do nothing. */ #define MEM_ALIGN_ATTR + /* Align pointer size */ + #define MEM_ALIGN_SIZE sizeof(intptr_t) #endif #ifdef STORAGE_WANTS_ALIGN