/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2007 Dave Chapman * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "codeclib.h" #define ROCKBOX #include CODEC_HEADER #define BLOCKS_PER_LOOP 1024 #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 */ static int32_t decoded0[BLOCKS_PER_LOOP] IBSS_ATTR; 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) { struct ape_ctx_t ape_ctx; uint32_t samplesdone; uint32_t elapsedtime; size_t bytesleft; int retval; uint32_t currentframe; uint32_t newfilepos; uint32_t samplestoskip; int nblocks; int bytesconsumed; unsigned char* inbuffer; uint32_t blockstodecode; int res; int firstbyte; size_t resume_offset; /* Generic codec initialisation */ ci->configure(CODEC_SET_FILEBUF_WATERMARK, 1024*512); 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"); retval = CODEC_ERROR; goto exit; } while (!*ci->taginfo_ready && !ci->stop_codec) ci->sleep(1); inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); /* Read the file headers to populate the ape_ctx struct */ if (ape_parseheaderbuf(inbuffer,&ape_ctx) < 0) { LOGF("APE: Error reading header\n"); retval = CODEC_ERROR; goto exit; } /* 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))); ci->configure(DSP_SWITCH_FREQUENCY, ape_ctx.samplerate); ci->configure(DSP_SET_STEREO_MODE, ape_ctx.channels == 1 ? STEREO_MONO : STEREO_NONINTERLEAVED); codec_set_replaygain(ci->id3); /* The main decoding loop */ 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); /* 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; else nblocks = ape_ctx.blocksperframe; ape_ctx.currentframeblocks = nblocks; /* Initialise the frame decoder */ init_frame_decoder(&ape_ctx, inbuffer, &firstbyte, &bytesconsumed); ci->advance_buffer(bytesconsumed); inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); /* Decode the frame a chunk at a time */ while (nblocks > 0) { ci->yield(); if (ci->stop_codec || ci->new_track) { 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); if ((res = decode_chunk(&ape_ctx, inbuffer, &firstbyte, &bytesconsumed, decoded0, decoded1, blockstodecode)) < 0) { /* Frame decoding error, abort */ LOGF("APE: Frame %d, error %d\n",currentframe,res); retval = CODEC_ERROR; goto done; } ci->yield(); 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); } samplesdone += blockstodecode; 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); /* Decrement the block count */ nblocks -= blockstodecode; } currentframe++; } retval = CODEC_OK; done: LOGF("APE: Decoded %ld samples\n",samplesdone); if (ci->request_next_track()) goto next_track; exit: return retval; }