From 7b1d90a851ec2bad8fe1327b2594f2de9a33cc4a Mon Sep 17 00:00:00 2001 From: Dave Chapman Date: Fri, 8 Jun 2007 22:35:26 +0000 Subject: Seeking and resume support for Monkey's Audio git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13597 a1c6a512-1295-4272-9138-f99709370657 --- apps/codecs/ape.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 182 insertions(+), 10 deletions(-) (limited to 'apps/codecs/ape.c') diff --git a/apps/codecs/ape.c b/apps/codecs/ape.c index b77abc0c74..0506c0ca49 100644 --- a/apps/codecs/ape.c +++ b/apps/codecs/ape.c @@ -27,6 +27,21 @@ CODEC_HEADER #define MAX_CHANNELS 2 #define MAX_BYTESPERSAMPLE 3 +/* Monkey's Audio files have one seekpoint per frame. The framesize + varies between 73728 and 1179648 samples. + + At the smallest framesize, 30000 frames would be 50155 seconds of + audio - almost 14 hours. This should be enough for any file a user + would want to play in Rockbox, given the 2GB FAT filesize (and 4GB + seektable entry size) limit. + + This means the seektable is 120000 bytes, but we have a lot of + spare room in the codec buffer - the APE codec itself is small. +*/ + +#define MAX_SEEKPOINTS 30000 +static uint32_t seektablebuf[MAX_SEEKPOINTS]; + #define INPUT_CHUNKSIZE (32*1024) /* 4608*4 = 18432 bytes per channel */ @@ -35,6 +50,81 @@ static int32_t decoded1[BLOCKS_PER_LOOP] IBSS_ATTR; #define MAX_SUPPORTED_SEEKTABLE_SIZE 5000 + +/* Given an ape_ctx and a sample to seek to, return the file position + to the frame containing that sample, and the number of samples to + skip in that frame. +*/ + +bool ape_calc_seekpos(struct ape_ctx_t* ape_ctx, + uint32_t new_sample, + uint32_t* newframe, + uint32_t* filepos, + uint32_t* samplestoskip) +{ + uint32_t n; + + n = new_sample / ape_ctx->blocksperframe; + if (n >= ape_ctx->numseekpoints) + { + /* We don't have a seekpoint for that frame */ + return false; + } + + *newframe = n; + *filepos = ape_ctx->seektable[n]; + *samplestoskip = new_sample - (n * ape_ctx->blocksperframe); + + return true; +} + +/* The resume offset is a value in bytes - we need to + turn it into a frame number and samplestoskip value */ + +void ape_resume(struct ape_ctx_t* ape_ctx, size_t resume_offset, + uint32_t* currentframe, uint32_t* samplesdone, + uint32_t* samplestoskip, int* firstbyte) +{ + off_t newfilepos; + int64_t framesize; + int64_t offset; + + *currentframe = 0; + *samplesdone = 0; + *samplestoskip = 0; + + while ((*currentframe < ape_ctx->totalframes) && + (*currentframe < ape_ctx->numseekpoints) && + (resume_offset > ape_ctx->seektable[*currentframe])) + { + ++*currentframe; + *samplesdone += ape_ctx->blocksperframe; + } + + if ((*currentframe > 0) && + (ape_ctx->seektable[*currentframe] > resume_offset)) { + --*currentframe; + *samplesdone -= ape_ctx->blocksperframe; + } + + newfilepos = ape_ctx->seektable[*currentframe]; + + /* APE's bytestream is weird... */ + *firstbyte = 3 - (newfilepos & 3); + newfilepos &= ~3; + + ci->seek_buffer(newfilepos); + + /* We estimate where we were in the current frame, based on the + byte offset */ + if (*currentframe < (ape_ctx->totalframes - 1)) { + framesize = ape_ctx->seektable[*currentframe+1] - ape_ctx->seektable[*currentframe]; + offset = resume_offset - ape_ctx->seektable[*currentframe]; + + *samplestoskip = (offset * ape_ctx->blocksperframe) / framesize; + } +} + /* this is the codec entry point */ enum codec_status codec_main(void) { @@ -45,12 +135,15 @@ enum codec_status codec_main(void) int retval; uint32_t currentframe; + uint32_t newfilepos; + uint32_t samplestoskip; int nblocks; int bytesconsumed; unsigned char* inbuffer; - int blockstodecode; + uint32_t blockstodecode; int res; int firstbyte; + size_t resume_offset; /* Generic codec initialisation */ ci->configure(CODEC_SET_FILEBUF_WATERMARK, 1024*512); @@ -59,6 +152,12 @@ enum codec_status codec_main(void) ci->configure(DSP_SET_SAMPLE_DEPTH, APE_OUTPUT_DEPTH-1); next_track: + + retval = CODEC_OK; + + /* Remember the resume position - when the codec is opened, the + playback engine will reset it. */ + resume_offset = ci->id3->offset; if (codec_init()) { LOGF("APE: Error initialising codec\n"); @@ -74,7 +173,31 @@ enum codec_status codec_main(void) retval = CODEC_ERROR; goto exit; } - ci->advance_buffer(ape_ctx.firstframe); + + /* Initialise the seektable for this file */ + ape_ctx.seektable = seektablebuf; + ape_ctx.numseekpoints = MIN(MAX_SEEKPOINTS,ape_ctx.numseekpoints); + + ci->advance_buffer(ape_ctx.seektablefilepos); + + /* The seektable may be bigger than the guard buffer (32KB), so we + do a read() */ + ci->read_filebuf(ape_ctx.seektable, ape_ctx.numseekpoints * sizeof(uint32_t)); + +#ifdef ROCKBOX_BIG_ENDIAN + /* Byte-swap the little-endian seekpoints */ + { + uint32_t i; + + for(i = 0; i < ape_ctx.numseekpoints; i++) + ape_ctx.seektable[i] = swap32(ape_ctx.seektable[i]); + } +#endif + + /* Now advance the file position to the first frame */ + ci->advance_buffer(ape_ctx.firstframe - + (ape_ctx.seektablefilepos + + ape_ctx.numseekpoints * sizeof(uint32_t))); while (!*ci->taginfo_ready && !ci->stop_codec) ci->sleep(1); @@ -86,16 +209,26 @@ enum codec_status codec_main(void) /* The main decoding loop */ - currentframe = 0; - samplesdone = 0; + if (resume_offset) { + /* The resume offset is a value in bytes - we need to + turn it into a frame number and samplestoskip value */ + + ape_resume(&ape_ctx, resume_offset, + ¤tframe, &samplesdone, &samplestoskip, &firstbyte); + } else { + currentframe = 0; + samplesdone = 0; + samplestoskip = 0; + firstbyte = 3; /* Take account of the little-endian 32-bit byte ordering */ + } /* Initialise the buffer */ inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); - firstbyte = 3; /* Take account of the little-endian 32-bit byte ordering */ /* The main decoding loop - we decode the frames a small chunk at a time */ while (currentframe < ape_ctx.totalframes) { +frame_start: /* Calculate how many blocks there are in this frame */ if (currentframe == (ape_ctx.totalframes - 1)) nblocks = ape_ctx.finalframeblocks; @@ -115,7 +248,31 @@ enum codec_status codec_main(void) { ci->yield(); if (ci->stop_codec || ci->new_track) { - break; + goto done; + } + + /* Deal with any pending seek requests */ + if (ci->seek_time) + { + if (ape_calc_seekpos(&ape_ctx, + ((ci->seek_time-1)/10) * (ci->id3->frequency/100), + ¤tframe, + &newfilepos, + &samplestoskip)) + { + samplesdone = currentframe * ape_ctx.blocksperframe; + + /* APE's bytestream is weird... */ + firstbyte = 3 - (newfilepos & 3); + newfilepos &= ~3; + + ci->seek_buffer(newfilepos); + inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); + + ci->seek_complete(); + goto frame_start; /* Sorry... */ + } + ci->seek_complete(); } blockstodecode = MIN(BLOCKS_PER_LOOP, nblocks); @@ -132,12 +289,27 @@ enum codec_status codec_main(void) } ci->yield(); - ci->pcmbuf_insert(decoded0, decoded1, blockstodecode); + + if (samplestoskip > 0) { + if (samplestoskip < blockstodecode) { + ci->pcmbuf_insert(decoded0 + samplestoskip, + decoded1 + samplestoskip, + blockstodecode - samplestoskip); + samplestoskip = 0; + } else { + samplestoskip -= blockstodecode; + } + } else { + ci->pcmbuf_insert(decoded0, decoded1, blockstodecode); + } - /* Update the elapsed-time indicator */ samplesdone += blockstodecode; - elapsedtime = (samplesdone*10)/(ape_ctx.samplerate/100); - ci->set_elapsed(elapsedtime); + + if (!samplestoskip) { + /* Update the elapsed-time indicator */ + elapsedtime = (samplesdone*10)/(ape_ctx.samplerate/100); + ci->set_elapsed(elapsedtime); + } ci->advance_buffer(bytesconsumed); inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); -- cgit v1.2.3