From 7ad2cad173ffa094bb285112582afee1c9aea4e5 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sun, 28 Aug 2011 07:45:35 +0000 Subject: 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 --- apps/codec_thread.c | 18 +- apps/codecs/a52.c | 2 +- apps/codecs/a52_rm.c | 1 + apps/codecs/aac.c | 5 +- apps/codecs/adx.c | 7 +- apps/codecs/aiff.c | 2 + apps/codecs/alac.c | 2 + apps/codecs/ape.c | 3 + apps/codecs/au.c | 2 + apps/codecs/cook.c | 4 +- apps/codecs/flac.c | 4 +- apps/codecs/mod.c | 3 +- apps/codecs/mpa.c | 12 +- apps/codecs/mpc.c | 4 + apps/codecs/shorten.c | 3 +- apps/codecs/sid.c | 2 +- apps/codecs/smaf.c | 2 + apps/codecs/spc.c | 2 + apps/codecs/speex.c | 1 + apps/codecs/tta.c | 2 + apps/codecs/vorbis.c | 3 + apps/codecs/vox.c | 2 + apps/codecs/wav.c | 2 + apps/codecs/wav64.c | 2 + apps/codecs/wavpack.c | 2 +- apps/codecs/wma.c | 3 +- apps/codecs/wmapro.c | 1 + apps/codecs/wmavoice.c | 2 + apps/pcmbuf.c | 1415 +++++++++++++++++++++++++--------------------- apps/pcmbuf.h | 29 +- apps/playback.c | 211 +++---- firmware/export/system.h | 4 + 32 files changed, 948 insertions(+), 809 deletions(-) diff --git a/apps/codec_thread.c b/apps/codec_thread.c index 945f0b0605..199bb0e742 100644 --- a/apps/codec_thread.c +++ b/apps/codec_thread.c @@ -77,9 +77,10 @@ struct codec_load_info 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 @@ static void codec_pcmbuf_insert_callback( if (out_count <= 0) return; - pcmbuf_write_complete(out_count); + pcmbuf_write_complete(out_count, ci.id3->elapsed, ci.id3->offset); count -= inp_count; } @@ -334,9 +335,11 @@ static void codec_seek_complete_callback(void) /* Clear DSP */ dsp_configure(ci.dsp, DSP_FLUSH, 0); + /* Sync position */ + audio_codec_update_offset(ci.curpos); + /* 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 +524,7 @@ static void run_codec(void) /* 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); } } diff --git a/apps/codecs/a52.c b/apps/codecs/a52.c index 4cd293e37f..641e2d05da 100644 --- a/apps/codecs/a52.c +++ b/apps/codecs/a52.c @@ -158,7 +158,7 @@ enum codec_status codec_run(void) } else { ci->seek_buffer(ci->id3->first_frame_offset); - samplesdone = 0; + ci->set_elapsed(0); } while (1) { diff --git a/apps/codecs/a52_rm.c b/apps/codecs/a52_rm.c index c1930aa7b4..0aa3edca3a 100644 --- a/apps/codecs/a52_rm.c +++ b/apps/codecs/a52_rm.c @@ -178,6 +178,7 @@ enum codec_status codec_run(void) } else { /* Seek to the first packet */ + ci->set_elapsed(0); ci->advance_buffer(rmctx.data_offset + DATA_HEADER_SIZE ); } diff --git a/apps/codecs/aac.c b/apps/codecs/aac.c index 52e08c7b56..f1e81bad81 100644 --- a/apps/codecs/aac.c +++ b/apps/codecs/aac.c @@ -134,8 +134,6 @@ enum codec_status codec_run(void) 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 @@ enum codec_status codec_run(void) } else { sound_samples_done = 0; } + + elapsed_time = (sound_samples_done * 10) / (ci->id3->frequency / 100); + ci->set_elapsed(elapsed_time); if (i == 0) { diff --git a/apps/codecs/adx.c b/apps/codecs/adx.c index e75e7dca1e..8144d5f468 100644 --- a/apps/codecs/adx.c +++ b/apps/codecs/adx.c @@ -209,7 +209,7 @@ enum codec_status codec_run(void) /* get in position */ ci->seek_buffer(bufoff); - + ci->set_elapsed(0); /* setup pcm buffer format */ ci->configure(DSP_SWITCH_FREQUENCY, ci->id3->frequency); @@ -276,6 +276,11 @@ enum codec_status codec_run(void) loop_count++; } ci->seek_buffer(bufoff); + + ci->set_elapsed( + ((end_adr-start_adr)*loop_count + bufoff-chanstart)* + 1000LL/avgbytespersec); + ci->seek_complete(); } diff --git a/apps/codecs/aiff.c b/apps/codecs/aiff.c index a8185b4910..333bcd0455 100644 --- a/apps/codecs/aiff.c +++ b/apps/codecs/aiff.c @@ -288,6 +288,8 @@ enum codec_status codec_run(void) bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; diff --git a/apps/codecs/alac.c b/apps/codecs/alac.c index b6852f5b3f..144d796e5f 100644 --- a/apps/codecs/alac.c +++ b/apps/codecs/alac.c @@ -97,6 +97,8 @@ enum codec_status codec_run(void) } } + 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); diff --git a/apps/codecs/ape.c b/apps/codecs/ape.c index 8f95a01ec7..ed6ea21685 100644 --- a/apps/codecs/ape.c +++ b/apps/codecs/ape.c @@ -220,6 +220,9 @@ enum codec_status codec_run(void) 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); diff --git a/apps/codecs/au.c b/apps/codecs/au.c index ef308358f4..cb75c7423d 100644 --- a/apps/codecs/au.c +++ b/apps/codecs/au.c @@ -253,6 +253,8 @@ enum codec_status codec_run(void) bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; diff --git a/apps/codecs/cook.c b/apps/codecs/cook.c index 4a47e74f60..55188aad36 100644 --- a/apps/codecs/cook.c +++ b/apps/codecs/cook.c @@ -105,8 +105,10 @@ enum codec_status codec_run(void) 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 */ diff --git a/apps/codecs/flac.c b/apps/codecs/flac.c index a5521b584f..c91a173f4a 100644 --- a/apps/codecs/flac.c +++ b/apps/codecs/flac.c @@ -460,7 +460,9 @@ enum codec_status codec_run(void) 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; diff --git a/apps/codecs/mod.c b/apps/codecs/mod.c index 3703ecd304..5bd64994f1 100644 --- a/apps/codecs/mod.c +++ b/apps/codecs/mod.c @@ -1333,12 +1333,11 @@ enum codec_status codec_run(void) /* 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; } diff --git a/apps/codecs/mpa.c b/apps/codecs/mpa.c index c9e2131450..ac81f06cab 100644 --- a/apps/codecs/mpa.c +++ b/apps/codecs/mpa.c @@ -144,6 +144,7 @@ static void set_elapsed(struct mp3entry* id3) { 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 @@ static void set_elapsed(struct mp3entry* id3) /* 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 diff --git a/apps/codecs/mpc.c b/apps/codecs/mpc.c index 7388799ee8..a405c72f79 100644 --- a/apps/codecs/mpc.c +++ b/apps/codecs/mpc.c @@ -123,6 +123,8 @@ enum codec_status codec_run(void) 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 @@ enum codec_status codec_run(void) } } + ci->set_elapsed(elapsed_time); + /* This is the decoding loop. */ do { diff --git a/apps/codecs/shorten.c b/apps/codecs/shorten.c index db66991679..a8ab3f30a0 100644 --- a/apps/codecs/shorten.c +++ b/apps/codecs/shorten.c @@ -99,6 +99,8 @@ enum codec_status codec_run(void) 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 @@ seek_start: 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; } diff --git a/apps/codecs/sid.c b/apps/codecs/sid.c index fed1e8acaa..9b19a20ba7 100644 --- a/apps/codecs/sid.c +++ b/apps/codecs/sid.c @@ -1299,8 +1299,8 @@ enum codec_status codec_run(void) nSamplesToRender = 0; /* Start the rendering from scratch */ /* Set the elapsed time to the current subsong (in seconds) */ - ci->seek_complete(); ci->set_elapsed(subSong*1000); + ci->seek_complete(); } nSamplesRendered = 0; diff --git a/apps/codecs/smaf.c b/apps/codecs/smaf.c index 9211daa9aa..0203d1b4e3 100644 --- a/apps/codecs/smaf.c +++ b/apps/codecs/smaf.c @@ -429,6 +429,8 @@ enum codec_status codec_run(void) bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; diff --git a/apps/codecs/spc.c b/apps/codecs/spc.c index 6b21f9ad19..3b007471a9 100644 --- a/apps/codecs/spc.c +++ b/apps/codecs/spc.c @@ -560,6 +560,8 @@ enum codec_status codec_run(void) return CODEC_ERROR; DEBUGF("SPC: read size = 0x%lx\n",(unsigned long)buffersize); + ci->set_elapsed(0); + do { if (load_spc_buffer(buffer, buffersize)) { diff --git a/apps/codecs/speex.c b/apps/codecs/speex.c index c01bcfdf9c..3d82ce7ad0 100644 --- a/apps/codecs/speex.c +++ b/apps/codecs/speex.c @@ -417,6 +417,7 @@ enum codec_status codec_run(void) } ci->seek_buffer(0); + ci->set_elapsed(0); stereo = speex_stereo_state_init(); spx_ogg_sync_init(&oy); diff --git a/apps/codecs/tta.c b/apps/codecs/tta.c index c75f2b0a57..dda33b3f5e 100644 --- a/apps/codecs/tta.c +++ b/apps/codecs/tta.c @@ -90,6 +90,8 @@ enum codec_status codec_run(void) 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); diff --git a/apps/codecs/vorbis.c b/apps/codecs/vorbis.c index e02d459262..fb1c9d7f7c 100644 --- a/apps/codecs/vorbis.c +++ b/apps/codecs/vorbis.c @@ -196,6 +196,9 @@ enum codec_status codec_run(void) ci->set_elapsed(ov_time_tell(&vf)); ci->set_offset(ov_raw_tell(&vf)); } + else { + ci->set_elapsed(0); + } previous_section = -1; eof = 0; diff --git a/apps/codecs/vox.c b/apps/codecs/vox.c index bf274c6917..279d003162 100644 --- a/apps/codecs/vox.c +++ b/apps/codecs/vox.c @@ -141,6 +141,8 @@ enum codec_status codec_run(void) bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; diff --git a/apps/codecs/wav.c b/apps/codecs/wav.c index f6f83b174d..d20331bc6c 100644 --- a/apps/codecs/wav.c +++ b/apps/codecs/wav.c @@ -378,6 +378,8 @@ enum codec_status codec_run(void) bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; diff --git a/apps/codecs/wav64.c b/apps/codecs/wav64.c index c763e6f7f0..184f39bf18 100644 --- a/apps/codecs/wav64.c +++ b/apps/codecs/wav64.c @@ -381,6 +381,8 @@ enum codec_status codec_run(void) bytesdone = 0; } + ci->set_elapsed(decodedsamples*1000LL/ci->id3->frequency); + /* The main decoder loop */ endofstream = 0; diff --git a/apps/codecs/wavpack.c b/apps/codecs/wavpack.c index 32f09d53e4..4d42391fc1 100644 --- a/apps/codecs/wavpack.c +++ b/apps/codecs/wavpack.c @@ -75,7 +75,7 @@ enum codec_status codec_run(void) 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 */ diff --git a/apps/codecs/wma.c b/apps/codecs/wma.c index c327fafb5a..8986531744 100644 --- a/apps/codecs/wma.c +++ b/apps/codecs/wma.c @@ -84,7 +84,6 @@ restart_track: % wfx.packet_size; ci->seek_buffer(resume_offset - packet_offset); elapsedtime = asf_get_timestamp(&i); - ci->set_elapsed(elapsedtime); } else { @@ -93,6 +92,8 @@ restart_track: 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 ? diff --git a/apps/codecs/wmapro.c b/apps/codecs/wmapro.c index 17e311c5c0..bab3b5a027 100644 --- a/apps/codecs/wmapro.c +++ b/apps/codecs/wmapro.c @@ -79,6 +79,7 @@ restart_track: ci->seek_buffer(ci->id3->first_frame_offset); elapsedtime = 0; + ci->set_elapsed(0); /* The main decoding loop */ diff --git a/apps/codecs/wmavoice.c b/apps/codecs/wmavoice.c index 64c8cd1692..9cf5a49f1a 100644 --- a/apps/codecs/wmavoice.c +++ b/apps/codecs/wmavoice.c @@ -109,6 +109,8 @@ restart_track: ci->seek_buffer(ci->id3->first_frame_offset); elapsedtime = 0; + ci->set_elapsed(0); + resume_offset = 0; /* The main decoding loop */ 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 #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(©_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; } diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h index a5cd3163e1..3261b20b35 100644 --- a/apps/pcmbuf.h +++ b/apps/pcmbuf.h @@ -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); @@ -33,20 +35,30 @@ void pcmbuf_play_start(void); void pcmbuf_play_stop(void); void pcmbuf_pause(bool pause); void pcmbuf_monitor_track_change(bool monitor); -bool pcmbuf_start_track_change(bool manual_skip); +void pcmbuf_sync_position_update(void); + +/* Track change origin type */ +enum pcm_track_change_type +{ + TRACK_CHANGE_NONE = 0, /* No track change pending */ + TRACK_CHANGE_MANUAL, /* Manual change (from user) */ + TRACK_CHANGE_AUTO, /* Automatic change (from codec) */ + TRACK_CHANGE_END_OF_DATA, /* Expect no more data (from codec) */ +}; +void pcmbuf_start_track_change(enum pcm_track_change_type type); /* 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,9 +71,7 @@ size_t pcmbuf_free(void); 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 +unsigned int pcmbuf_get_position_key(void); /* Misc */ void pcmbuf_fade(bool fade, bool in); @@ -69,6 +79,5 @@ bool pcmbuf_fading(void); 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 diff --git a/apps/playback.c b/apps/playback.c index 65783414fe..7dad08644a 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -330,7 +330,7 @@ static struct static bool codec_skip_pending = false; static int codec_skip_status; static bool codec_seeking = false; /* Codec seeking ack expected? */ - +static unsigned int position_key = 0; /* Event queues */ static struct event_queue audio_queue SHAREDBSS_ATTR; @@ -353,14 +353,13 @@ static void audio_stop_playback(void); static void buffer_event_buffer_low_callback(void *data); static void buffer_event_rebuffer_callback(void *data); static void buffer_event_finished_callback(void *data); +void audio_pcmbuf_sync_position(void); /**************************************/ /** --- 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); } @@ -805,14 +804,10 @@ static void audio_reset_buffer(void) 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 @@ -978,7 +973,8 @@ static void audio_handle_track_load_status(int trackstat) /* 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); @@ -1001,6 +997,8 @@ static void audio_playlist_track_change(void) if (id3) send_event(PLAYBACK_EVENT_TRACK_CHANGE, id3); + position_key = pcmbuf_get_position_key(); + playlist_update_resume_info(id3); } @@ -1014,26 +1012,28 @@ static void audio_update_and_announce_next_track(const struct mp3entry *id3_next /* 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); + struct mp3entry *playing_id3 = id3_get(PLAYING_ID3); - if (offset == (size_t)-1) + pcm_play_lock(); + + unsigned long e = playing_id3->elapsed; + unsigned long o = playing_id3->offset; + + id3_write(PLAYING_ID3, id3); + + if (offset < 0) { - 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; + playing_id3->elapsed = e; + playing_id3->offset = o; offset = 0; } - else - { - id3_write(PLAYING_ID3, id3); - } + + pcm_play_unlock(); if (id3) id3->offset = offset; @@ -1093,13 +1093,6 @@ static bool halt_decoding_track(bool stop) 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(); -} - /* Wait for any in-progress fade to complete */ static void audio_wait_fade_complete(void) { @@ -1121,6 +1114,7 @@ static void audio_ff_rewind_end(void) { /* Clear the buffer */ pcmbuf_play_stop(); + audio_pcmbuf_sync_position(); } if (play_status != PLAY_PAUSED) @@ -2063,7 +2057,7 @@ static void audio_on_handle_finished(int hid) /* 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) { @@ -2117,15 +2111,6 @@ static void audio_finalise_track_change(bool delayed) 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; @@ -2141,25 +2126,25 @@ static void audio_finalise_track_change(bool delayed) } /* Actually begin a transition and take care of the codec change - may complete - it now or ask pcmbuf for notification depending on the type and what pcmbuf - has to say */ -static void audio_begin_track_change(bool auto_skip, int trackstat) + it now or ask pcmbuf for notification depending on the type */ +static void audio_begin_track_change(enum pcm_track_change_type type, + int trackstat) { /* Even if the new track is bad, the old track must be finished off */ - bool finalised = pcmbuf_start_track_change(auto_skip); + pcmbuf_start_track_change(type); + + bool auto_skip = type != TRACK_CHANGE_MANUAL; - if (finalised) + if (!auto_skip) { - /* pcmbuf says that the transition happens now - complete it */ - audio_finalise_track_change(false); + /* Manual track change happens now */ + audio_finalise_track_change(); + pcmbuf_sync_position_update(); 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); @@ -2170,7 +2155,7 @@ static void audio_begin_track_change(bool auto_skip, int trackstat) /* Everything needed for the codec is ready - start it */ if (audio_start_codec(auto_skip)) { - if (finalised) + if (!auto_skip) playing_id3_sync(info, -1); return; } @@ -2186,7 +2171,7 @@ static void audio_monitor_end_of_playlist(void) { skip_pending = TRACK_SKIP_AUTO_END_PLAYLIST; filling = STATE_ENDING; - pcmbuf_monitor_track_change(true); + pcmbuf_start_track_change(TRACK_CHANGE_END_OF_DATA); } /* Codec has completed decoding the track @@ -2221,14 +2206,6 @@ static void audio_on_codec_complete(int status) 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; @@ -2263,7 +2240,7 @@ static void audio_on_codec_complete(int status) { /* Continue filling after this track */ audio_reset_and_rebuffer(TRACK_LIST_KEEP_CURRENT, 1); - audio_begin_track_change(true, trackstat); + audio_begin_track_change(TRACK_CHANGE_AUTO, trackstat); return; } /* else rebuffer at this track; status applies to the track we @@ -2299,7 +2276,7 @@ static void audio_on_codec_complete(int status) } } - audio_begin_track_change(true, trackstat); + audio_begin_track_change(TRACK_CHANGE_AUTO, trackstat); } /* Called when codec completes seek operation @@ -2316,7 +2293,7 @@ static void audio_on_codec_seek_complete(void) 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) { @@ -2367,8 +2344,7 @@ static void audio_start_playback(size_t offset, unsigned int flags) track_list_clear(TRACK_LIST_CLEAR_ALL); /* Indicate manual track change */ - pcmbuf_start_track_change(false); - audio_clear_paused_pcm(); + pcmbuf_start_track_change(TRACK_CHANGE_MANUAL); wipe_track_metadata(true); } @@ -2398,6 +2374,10 @@ static void audio_start_playback(size_t offset, unsigned int flags) play_status = PLAY_PLAYING; } + /* Codec's position should be available as soon as it knows it */ + position_key = pcmbuf_get_position_key(); + pcmbuf_sync_position_update(); + /* Start fill from beginning of playlist */ playlist_peek_offset = -1; buf_set_base_handle(-1); @@ -2592,7 +2572,7 @@ static void audio_on_skip(void) trackstat = audio_reset_and_rebuffer(TRACK_LIST_CLEAR_ALL, -1); } - audio_begin_track_change(false, trackstat); + audio_begin_track_change(TRACK_CHANGE_MANUAL, trackstat); } /* Skip to the next/previous directory @@ -2638,7 +2618,7 @@ static void audio_on_dir_skip(int direction) return; } - audio_begin_track_change(false, trackstat); + audio_begin_track_change(TRACK_CHANGE_MANUAL, trackstat); } /* Enter seek mode in order to start a seek @@ -2689,11 +2669,6 @@ static void audio_on_ff_rewind(long time) if (time == 0) send_event(PLAYBACK_EVENT_TRACK_FINISH, id3); - /* Prevent user codec time update - coerce to something that is - innocuous concerning lookaheads */ - if (pending == TRACK_SKIP_NONE) - skip_pending = TRACK_SKIP_AUTO_END_PLAYLIST; - id3->elapsed = time; queue_reply(&audio_queue, 1); @@ -2703,6 +2678,9 @@ static void audio_on_ff_rewind(long time) halt that will reset it */ codec_seeking = true; + /* If in transition, key will have changed - sync to it */ + position_key = pcmbuf_get_position_key(); + if (pending == TRACK_SKIP_AUTO) { if (!track_list_advance_current(-1)) @@ -3124,75 +3102,66 @@ static void buffer_event_finished_callback(void *data) /** -- 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)) - { - value -= latency; - } - else +/* Codec has finished running */ +void audio_codec_complete(int status) +{ +#ifdef AB_REPEAT_ENABLE + if (status >= CODEC_OK) { - value = 0; + /* Normal automatic skip */ + ab_end_of_track_report(); } +#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, + unsigned int key) { - struct mp3entry *id3 = id3_get(PLAYING_ID3); - - time += id3->elapsed; + if (key == position_key) + { + struct mp3entry *id3 = id3_get(PLAYING_ID3); + id3->elapsed = elapsed; + id3->offset = offset; + } +} - id3->elapsed = MIN(time, id3->length); +/* Synchronize position info to the codec's */ +void audio_pcmbuf_sync_position(void) +{ + audio_pcmbuf_position_callback(ci.id3->elapsed, ci.id3->offset, + pcmbuf_get_position_key()); } /* Post message from pcmbuf that the end of the previous track has just diff --git a/firmware/export/system.h b/firmware/export/system.h index 5128801725..1f8ed033fd 100644 --- a/firmware/export/system.h +++ b/firmware/export/system.h @@ -378,12 +378,16 @@ static inline void cpucache_flush(void) #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 -- cgit v1.2.3