summaryrefslogtreecommitdiff
path: root/apps/codecs/ape.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/codecs/ape.c')
-rw-r--r--apps/codecs/ape.c192
1 files changed, 182 insertions, 10 deletions
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,
+ &currentframe, &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),
+ &currentframe,
+ &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);