diff options
author | Dave Chapman <dave@dchapman.com> | 2007-06-08 22:35:26 +0000 |
---|---|---|
committer | Dave Chapman <dave@dchapman.com> | 2007-06-08 22:35:26 +0000 |
commit | 7b1d90a851ec2bad8fe1327b2594f2de9a33cc4a (patch) | |
tree | 36d3acf40322d2bd0d4c77162d591557adced8a3 /apps/codecs/ape.c | |
parent | 691a0780b9b6c2a2b14bf1ea61e314a004225523 (diff) | |
download | rockbox-7b1d90a851ec2bad8fe1327b2594f2de9a33cc4a.tar.gz rockbox-7b1d90a851ec2bad8fe1327b2594f2de9a33cc4a.zip |
Seeking and resume support for Monkey's Audio
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13597 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/codecs/ape.c')
-rw-r--r-- | apps/codecs/ape.c | 192 |
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 | |||
27 | #define MAX_CHANNELS 2 | 27 | #define MAX_CHANNELS 2 |
28 | #define MAX_BYTESPERSAMPLE 3 | 28 | #define MAX_BYTESPERSAMPLE 3 |
29 | 29 | ||
30 | /* Monkey's Audio files have one seekpoint per frame. The framesize | ||
31 | varies between 73728 and 1179648 samples. | ||
32 | |||
33 | At the smallest framesize, 30000 frames would be 50155 seconds of | ||
34 | audio - almost 14 hours. This should be enough for any file a user | ||
35 | would want to play in Rockbox, given the 2GB FAT filesize (and 4GB | ||
36 | seektable entry size) limit. | ||
37 | |||
38 | This means the seektable is 120000 bytes, but we have a lot of | ||
39 | spare room in the codec buffer - the APE codec itself is small. | ||
40 | */ | ||
41 | |||
42 | #define MAX_SEEKPOINTS 30000 | ||
43 | static uint32_t seektablebuf[MAX_SEEKPOINTS]; | ||
44 | |||
30 | #define INPUT_CHUNKSIZE (32*1024) | 45 | #define INPUT_CHUNKSIZE (32*1024) |
31 | 46 | ||
32 | /* 4608*4 = 18432 bytes per channel */ | 47 | /* 4608*4 = 18432 bytes per channel */ |
@@ -35,6 +50,81 @@ static int32_t decoded1[BLOCKS_PER_LOOP] IBSS_ATTR; | |||
35 | 50 | ||
36 | #define MAX_SUPPORTED_SEEKTABLE_SIZE 5000 | 51 | #define MAX_SUPPORTED_SEEKTABLE_SIZE 5000 |
37 | 52 | ||
53 | |||
54 | /* Given an ape_ctx and a sample to seek to, return the file position | ||
55 | to the frame containing that sample, and the number of samples to | ||
56 | skip in that frame. | ||
57 | */ | ||
58 | |||
59 | bool ape_calc_seekpos(struct ape_ctx_t* ape_ctx, | ||
60 | uint32_t new_sample, | ||
61 | uint32_t* newframe, | ||
62 | uint32_t* filepos, | ||
63 | uint32_t* samplestoskip) | ||
64 | { | ||
65 | uint32_t n; | ||
66 | |||
67 | n = new_sample / ape_ctx->blocksperframe; | ||
68 | if (n >= ape_ctx->numseekpoints) | ||
69 | { | ||
70 | /* We don't have a seekpoint for that frame */ | ||
71 | return false; | ||
72 | } | ||
73 | |||
74 | *newframe = n; | ||
75 | *filepos = ape_ctx->seektable[n]; | ||
76 | *samplestoskip = new_sample - (n * ape_ctx->blocksperframe); | ||
77 | |||
78 | return true; | ||
79 | } | ||
80 | |||
81 | /* The resume offset is a value in bytes - we need to | ||
82 | turn it into a frame number and samplestoskip value */ | ||
83 | |||
84 | void ape_resume(struct ape_ctx_t* ape_ctx, size_t resume_offset, | ||
85 | uint32_t* currentframe, uint32_t* samplesdone, | ||
86 | uint32_t* samplestoskip, int* firstbyte) | ||
87 | { | ||
88 | off_t newfilepos; | ||
89 | int64_t framesize; | ||
90 | int64_t offset; | ||
91 | |||
92 | *currentframe = 0; | ||
93 | *samplesdone = 0; | ||
94 | *samplestoskip = 0; | ||
95 | |||
96 | while ((*currentframe < ape_ctx->totalframes) && | ||
97 | (*currentframe < ape_ctx->numseekpoints) && | ||
98 | (resume_offset > ape_ctx->seektable[*currentframe])) | ||
99 | { | ||
100 | ++*currentframe; | ||
101 | *samplesdone += ape_ctx->blocksperframe; | ||
102 | } | ||
103 | |||
104 | if ((*currentframe > 0) && | ||
105 | (ape_ctx->seektable[*currentframe] > resume_offset)) { | ||
106 | --*currentframe; | ||
107 | *samplesdone -= ape_ctx->blocksperframe; | ||
108 | } | ||
109 | |||
110 | newfilepos = ape_ctx->seektable[*currentframe]; | ||
111 | |||
112 | /* APE's bytestream is weird... */ | ||
113 | *firstbyte = 3 - (newfilepos & 3); | ||
114 | newfilepos &= ~3; | ||
115 | |||
116 | ci->seek_buffer(newfilepos); | ||
117 | |||
118 | /* We estimate where we were in the current frame, based on the | ||
119 | byte offset */ | ||
120 | if (*currentframe < (ape_ctx->totalframes - 1)) { | ||
121 | framesize = ape_ctx->seektable[*currentframe+1] - ape_ctx->seektable[*currentframe]; | ||
122 | offset = resume_offset - ape_ctx->seektable[*currentframe]; | ||
123 | |||
124 | *samplestoskip = (offset * ape_ctx->blocksperframe) / framesize; | ||
125 | } | ||
126 | } | ||
127 | |||
38 | /* this is the codec entry point */ | 128 | /* this is the codec entry point */ |
39 | enum codec_status codec_main(void) | 129 | enum codec_status codec_main(void) |
40 | { | 130 | { |
@@ -45,12 +135,15 @@ enum codec_status codec_main(void) | |||
45 | int retval; | 135 | int retval; |
46 | 136 | ||
47 | uint32_t currentframe; | 137 | uint32_t currentframe; |
138 | uint32_t newfilepos; | ||
139 | uint32_t samplestoskip; | ||
48 | int nblocks; | 140 | int nblocks; |
49 | int bytesconsumed; | 141 | int bytesconsumed; |
50 | unsigned char* inbuffer; | 142 | unsigned char* inbuffer; |
51 | int blockstodecode; | 143 | uint32_t blockstodecode; |
52 | int res; | 144 | int res; |
53 | int firstbyte; | 145 | int firstbyte; |
146 | size_t resume_offset; | ||
54 | 147 | ||
55 | /* Generic codec initialisation */ | 148 | /* Generic codec initialisation */ |
56 | ci->configure(CODEC_SET_FILEBUF_WATERMARK, 1024*512); | 149 | ci->configure(CODEC_SET_FILEBUF_WATERMARK, 1024*512); |
@@ -59,6 +152,12 @@ enum codec_status codec_main(void) | |||
59 | ci->configure(DSP_SET_SAMPLE_DEPTH, APE_OUTPUT_DEPTH-1); | 152 | ci->configure(DSP_SET_SAMPLE_DEPTH, APE_OUTPUT_DEPTH-1); |
60 | 153 | ||
61 | next_track: | 154 | next_track: |
155 | |||
156 | retval = CODEC_OK; | ||
157 | |||
158 | /* Remember the resume position - when the codec is opened, the | ||
159 | playback engine will reset it. */ | ||
160 | resume_offset = ci->id3->offset; | ||
62 | 161 | ||
63 | if (codec_init()) { | 162 | if (codec_init()) { |
64 | LOGF("APE: Error initialising codec\n"); | 163 | LOGF("APE: Error initialising codec\n"); |
@@ -74,7 +173,31 @@ enum codec_status codec_main(void) | |||
74 | retval = CODEC_ERROR; | 173 | retval = CODEC_ERROR; |
75 | goto exit; | 174 | goto exit; |
76 | } | 175 | } |
77 | ci->advance_buffer(ape_ctx.firstframe); | 176 | |
177 | /* Initialise the seektable for this file */ | ||
178 | ape_ctx.seektable = seektablebuf; | ||
179 | ape_ctx.numseekpoints = MIN(MAX_SEEKPOINTS,ape_ctx.numseekpoints); | ||
180 | |||
181 | ci->advance_buffer(ape_ctx.seektablefilepos); | ||
182 | |||
183 | /* The seektable may be bigger than the guard buffer (32KB), so we | ||
184 | do a read() */ | ||
185 | ci->read_filebuf(ape_ctx.seektable, ape_ctx.numseekpoints * sizeof(uint32_t)); | ||
186 | |||
187 | #ifdef ROCKBOX_BIG_ENDIAN | ||
188 | /* Byte-swap the little-endian seekpoints */ | ||
189 | { | ||
190 | uint32_t i; | ||
191 | |||
192 | for(i = 0; i < ape_ctx.numseekpoints; i++) | ||
193 | ape_ctx.seektable[i] = swap32(ape_ctx.seektable[i]); | ||
194 | } | ||
195 | #endif | ||
196 | |||
197 | /* Now advance the file position to the first frame */ | ||
198 | ci->advance_buffer(ape_ctx.firstframe - | ||
199 | (ape_ctx.seektablefilepos + | ||
200 | ape_ctx.numseekpoints * sizeof(uint32_t))); | ||
78 | 201 | ||
79 | while (!*ci->taginfo_ready && !ci->stop_codec) | 202 | while (!*ci->taginfo_ready && !ci->stop_codec) |
80 | ci->sleep(1); | 203 | ci->sleep(1); |
@@ -86,16 +209,26 @@ enum codec_status codec_main(void) | |||
86 | 209 | ||
87 | /* The main decoding loop */ | 210 | /* The main decoding loop */ |
88 | 211 | ||
89 | currentframe = 0; | 212 | if (resume_offset) { |
90 | samplesdone = 0; | 213 | /* The resume offset is a value in bytes - we need to |
214 | turn it into a frame number and samplestoskip value */ | ||
215 | |||
216 | ape_resume(&ape_ctx, resume_offset, | ||
217 | ¤tframe, &samplesdone, &samplestoskip, &firstbyte); | ||
218 | } else { | ||
219 | currentframe = 0; | ||
220 | samplesdone = 0; | ||
221 | samplestoskip = 0; | ||
222 | firstbyte = 3; /* Take account of the little-endian 32-bit byte ordering */ | ||
223 | } | ||
91 | 224 | ||
92 | /* Initialise the buffer */ | 225 | /* Initialise the buffer */ |
93 | inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); | 226 | inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); |
94 | firstbyte = 3; /* Take account of the little-endian 32-bit byte ordering */ | ||
95 | 227 | ||
96 | /* The main decoding loop - we decode the frames a small chunk at a time */ | 228 | /* The main decoding loop - we decode the frames a small chunk at a time */ |
97 | while (currentframe < ape_ctx.totalframes) | 229 | while (currentframe < ape_ctx.totalframes) |
98 | { | 230 | { |
231 | frame_start: | ||
99 | /* Calculate how many blocks there are in this frame */ | 232 | /* Calculate how many blocks there are in this frame */ |
100 | if (currentframe == (ape_ctx.totalframes - 1)) | 233 | if (currentframe == (ape_ctx.totalframes - 1)) |
101 | nblocks = ape_ctx.finalframeblocks; | 234 | nblocks = ape_ctx.finalframeblocks; |
@@ -115,7 +248,31 @@ enum codec_status codec_main(void) | |||
115 | { | 248 | { |
116 | ci->yield(); | 249 | ci->yield(); |
117 | if (ci->stop_codec || ci->new_track) { | 250 | if (ci->stop_codec || ci->new_track) { |
118 | break; | 251 | goto done; |
252 | } | ||
253 | |||
254 | /* Deal with any pending seek requests */ | ||
255 | if (ci->seek_time) | ||
256 | { | ||
257 | if (ape_calc_seekpos(&ape_ctx, | ||
258 | ((ci->seek_time-1)/10) * (ci->id3->frequency/100), | ||
259 | ¤tframe, | ||
260 | &newfilepos, | ||
261 | &samplestoskip)) | ||
262 | { | ||
263 | samplesdone = currentframe * ape_ctx.blocksperframe; | ||
264 | |||
265 | /* APE's bytestream is weird... */ | ||
266 | firstbyte = 3 - (newfilepos & 3); | ||
267 | newfilepos &= ~3; | ||
268 | |||
269 | ci->seek_buffer(newfilepos); | ||
270 | inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); | ||
271 | |||
272 | ci->seek_complete(); | ||
273 | goto frame_start; /* Sorry... */ | ||
274 | } | ||
275 | ci->seek_complete(); | ||
119 | } | 276 | } |
120 | 277 | ||
121 | blockstodecode = MIN(BLOCKS_PER_LOOP, nblocks); | 278 | blockstodecode = MIN(BLOCKS_PER_LOOP, nblocks); |
@@ -132,12 +289,27 @@ enum codec_status codec_main(void) | |||
132 | } | 289 | } |
133 | 290 | ||
134 | ci->yield(); | 291 | ci->yield(); |
135 | ci->pcmbuf_insert(decoded0, decoded1, blockstodecode); | 292 | |
293 | if (samplestoskip > 0) { | ||
294 | if (samplestoskip < blockstodecode) { | ||
295 | ci->pcmbuf_insert(decoded0 + samplestoskip, | ||
296 | decoded1 + samplestoskip, | ||
297 | blockstodecode - samplestoskip); | ||
298 | samplestoskip = 0; | ||
299 | } else { | ||
300 | samplestoskip -= blockstodecode; | ||
301 | } | ||
302 | } else { | ||
303 | ci->pcmbuf_insert(decoded0, decoded1, blockstodecode); | ||
304 | } | ||
136 | 305 | ||
137 | /* Update the elapsed-time indicator */ | ||
138 | samplesdone += blockstodecode; | 306 | samplesdone += blockstodecode; |
139 | elapsedtime = (samplesdone*10)/(ape_ctx.samplerate/100); | 307 | |
140 | ci->set_elapsed(elapsedtime); | 308 | if (!samplestoskip) { |
309 | /* Update the elapsed-time indicator */ | ||
310 | elapsedtime = (samplesdone*10)/(ape_ctx.samplerate/100); | ||
311 | ci->set_elapsed(elapsedtime); | ||
312 | } | ||
141 | 313 | ||
142 | ci->advance_buffer(bytesconsumed); | 314 | ci->advance_buffer(bytesconsumed); |
143 | inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); | 315 | inbuffer = ci->request_buffer(&bytesleft, INPUT_CHUNKSIZE); |