diff options
Diffstat (limited to 'apps/plugins/mpa2wav.c')
-rw-r--r-- | apps/plugins/mpa2wav.c | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/apps/plugins/mpa2wav.c b/apps/plugins/mpa2wav.c new file mode 100644 index 0000000000..bff7608d98 --- /dev/null +++ b/apps/plugins/mpa2wav.c | |||
@@ -0,0 +1,480 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2005 Dave Chapman | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | #include "plugin.h" | ||
21 | |||
22 | #include <codecs/libmad/mad.h> | ||
23 | |||
24 | typedef struct ao_sample_format { | ||
25 | int bits; /* bits per sample */ | ||
26 | int rate; /* samples per second (in a single channel) */ | ||
27 | int channels; /* number of audio channels */ | ||
28 | int byte_format; /* Byte ordering in sample, see constants below */ | ||
29 | } ao_sample_format; | ||
30 | |||
31 | typedef struct { | ||
32 | int infile; | ||
33 | int outfile; | ||
34 | off_t curpos; | ||
35 | off_t filesize; | ||
36 | ao_sample_format samfmt; /* bits, rate, channels, byte_format */ | ||
37 | int framesize; | ||
38 | unsigned long total_samples; | ||
39 | unsigned long current_sample; | ||
40 | } file_info_struct; | ||
41 | |||
42 | |||
43 | struct mad_stream Stream; | ||
44 | struct mad_frame Frame; | ||
45 | struct mad_synth Synth; | ||
46 | mad_timer_t Timer; | ||
47 | struct dither d0, d1; | ||
48 | |||
49 | file_info_struct file_info; | ||
50 | |||
51 | #define MALLOC_BUFSIZE (512*1024) | ||
52 | |||
53 | int mem_ptr; | ||
54 | int bufsize; | ||
55 | unsigned char* mp3buf; // The actual MP3 buffer from Rockbox | ||
56 | unsigned char* mallocbuf; // 512K from the start of MP3 buffer | ||
57 | unsigned char* filebuf; // The rest of the MP3 buffer | ||
58 | |||
59 | /* here is a global api struct pointer. while not strictly necessary, | ||
60 | it's nice not to have to pass the api pointer in all function calls | ||
61 | in the plugin */ | ||
62 | static struct plugin_api* rb; | ||
63 | |||
64 | void* malloc(size_t size) { | ||
65 | void* x; | ||
66 | char s[32]; | ||
67 | |||
68 | x=&mallocbuf[mem_ptr]; | ||
69 | mem_ptr+=size+(size%4); // Keep memory 32-bit aligned (if it was already?) | ||
70 | |||
71 | rb->snprintf(s,30,"Memory used: %d",mem_ptr); | ||
72 | rb->lcd_putsxy(0,80,s); | ||
73 | rb->lcd_update(); | ||
74 | return(x); | ||
75 | } | ||
76 | |||
77 | void* calloc(size_t nmemb, size_t size) { | ||
78 | void* x; | ||
79 | x=malloc(nmemb*size); | ||
80 | rb->memset(x,0,nmemb*size); | ||
81 | return(x); | ||
82 | } | ||
83 | |||
84 | void free(void* ptr) { | ||
85 | (void)ptr; | ||
86 | } | ||
87 | |||
88 | void* realloc(void* ptr, size_t size) { | ||
89 | void* x; | ||
90 | (void)ptr; | ||
91 | x=malloc(size); | ||
92 | return(x); | ||
93 | } | ||
94 | |||
95 | void *memcpy(void *dest, const void *src, size_t n) { | ||
96 | return(rb->memcpy(dest,src,n)); | ||
97 | } | ||
98 | |||
99 | void *memset(void *s, int c, size_t n) { | ||
100 | return(rb->memset(s,c,n)); | ||
101 | } | ||
102 | |||
103 | int memcmp(const void *s1, const void *s2, size_t n) { | ||
104 | return(rb->memcmp(s1,s2,n)); | ||
105 | } | ||
106 | |||
107 | void* memmove(const void *s1, const void *s2, size_t n) { | ||
108 | char* dest=(char*)s1; | ||
109 | char* src=(char*)s2; | ||
110 | size_t i; | ||
111 | |||
112 | for (i=0;i<n;i++) { dest[i]=src[i]; } | ||
113 | // while(n>0) { *(dest++)=*(src++); n--; } | ||
114 | return(dest); | ||
115 | } | ||
116 | |||
117 | void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *)) { | ||
118 | rb->qsort(base,nmemb,size,compar); | ||
119 | } | ||
120 | |||
121 | |||
122 | void abort(void) { | ||
123 | /* Let's hope this is never called by libmad */ | ||
124 | } | ||
125 | |||
126 | /* The "dither" code to convert the 24-bit samples produced by libmad was | ||
127 | taken from the coolplayer project - coolplayer.sourceforge.net */ | ||
128 | |||
129 | struct dither { | ||
130 | mad_fixed_t error[3]; | ||
131 | mad_fixed_t random; | ||
132 | }; | ||
133 | |||
134 | # define SAMPLE_DEPTH 16 | ||
135 | # define scale(x, y) dither((x), (y)) | ||
136 | |||
137 | /* | ||
138 | * NAME: prng() | ||
139 | * DESCRIPTION: 32-bit pseudo-random number generator | ||
140 | */ | ||
141 | static __inline | ||
142 | unsigned long prng(unsigned long state) | ||
143 | { | ||
144 | return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; | ||
145 | } | ||
146 | |||
147 | /* | ||
148 | * NAME: dither() | ||
149 | * DESCRIPTION: dither and scale sample | ||
150 | */ | ||
151 | static __inline | ||
152 | signed int dither(mad_fixed_t sample, struct dither *dither) | ||
153 | { | ||
154 | unsigned int scalebits; | ||
155 | mad_fixed_t output, mask, random; | ||
156 | |||
157 | enum { | ||
158 | MIN = -MAD_F_ONE, | ||
159 | MAX = MAD_F_ONE - 1 | ||
160 | }; | ||
161 | |||
162 | /* noise shape */ | ||
163 | sample += dither->error[0] - dither->error[1] + dither->error[2]; | ||
164 | |||
165 | dither->error[2] = dither->error[1]; | ||
166 | dither->error[1] = dither->error[0] / 2; | ||
167 | |||
168 | /* bias */ | ||
169 | output = sample + (1L << (MAD_F_FRACBITS + 1 - SAMPLE_DEPTH - 1)); | ||
170 | |||
171 | scalebits = MAD_F_FRACBITS + 1 - SAMPLE_DEPTH; | ||
172 | mask = (1L << scalebits) - 1; | ||
173 | |||
174 | /* dither */ | ||
175 | random = prng(dither->random); | ||
176 | output += (random & mask) - (dither->random & mask); | ||
177 | |||
178 | dither->random = random; | ||
179 | |||
180 | /* clip */ | ||
181 | if (output > MAX) { | ||
182 | output = MAX; | ||
183 | |||
184 | if (sample > MAX) | ||
185 | sample = MAX; | ||
186 | } | ||
187 | else if (output < MIN) { | ||
188 | output = MIN; | ||
189 | |||
190 | if (sample < MIN) | ||
191 | sample = MIN; | ||
192 | } | ||
193 | |||
194 | /* quantize */ | ||
195 | output &= ~mask; | ||
196 | |||
197 | /* error feedback */ | ||
198 | dither->error[0] = sample - output; | ||
199 | |||
200 | /* scale */ | ||
201 | return output >> scalebits; | ||
202 | } | ||
203 | |||
204 | #define SHRT_MAX 32767 | ||
205 | |||
206 | static unsigned char wav_header[44]={'R','I','F','F', // 0 - ChunkID | ||
207 | 0,0,0,0, // 4 - ChunkSize (filesize-8) | ||
208 | 'W','A','V','E', // 8 - Format | ||
209 | 'f','m','t',' ', // 12 - SubChunkID | ||
210 | 16,0,0,0, // 16 - SubChunk1ID // 16 for PCM | ||
211 | 1,0, // 20 - AudioFormat (1=16-bit) | ||
212 | 2,0, // 22 - NumChannels | ||
213 | 0,0,0,0, // 24 - SampleRate in Hz | ||
214 | 0,0,0,0, // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8) | ||
215 | 4,0, // 32 - BlockAlign (== NumChannels * BitsPerSample/8) | ||
216 | 16,0, // 34 - BitsPerSample | ||
217 | 'd','a','t','a', // 36 - Subchunk2ID | ||
218 | 0,0,0,0 // 40 - Subchunk2Size | ||
219 | }; | ||
220 | |||
221 | void close_wav(file_info_struct* file_info) { | ||
222 | int x; | ||
223 | int filesize=rb->filesize(file_info->outfile); | ||
224 | |||
225 | /* We assume 16-bit, Stereo */ | ||
226 | |||
227 | rb->lseek(file_info->outfile,0,SEEK_SET); | ||
228 | |||
229 | // ChunkSize | ||
230 | x=filesize-8; | ||
231 | wav_header[4]=(x&0xff); | ||
232 | wav_header[5]=(x&0xff00)>>8; | ||
233 | wav_header[6]=(x&0xff0000)>>16; | ||
234 | wav_header[7]=(x&0xff000000)>>24; | ||
235 | |||
236 | // Samplerate | ||
237 | wav_header[24]=file_info->samfmt.rate&0xff; | ||
238 | wav_header[25]=(file_info->samfmt.rate&0xff00)>>8; | ||
239 | wav_header[26]=(file_info->samfmt.rate&0xff0000)>>16; | ||
240 | wav_header[27]=(file_info->samfmt.rate&0xff000000)>>24; | ||
241 | |||
242 | // ByteRate | ||
243 | x=file_info->samfmt.rate*4; | ||
244 | wav_header[28]=(x&0xff); | ||
245 | wav_header[29]=(x&0xff00)>>8; | ||
246 | wav_header[30]=(x&0xff0000)>>16; | ||
247 | wav_header[31]=(x&0xff000000)>>24; | ||
248 | |||
249 | // Subchunk2Size | ||
250 | x=filesize-44; | ||
251 | wav_header[40]=(x&0xff); | ||
252 | wav_header[41]=(x&0xff00)>>8; | ||
253 | wav_header[42]=(x&0xff0000)>>16; | ||
254 | wav_header[43]=(x&0xff000000)>>24; | ||
255 | |||
256 | rb->write(file_info->outfile,wav_header,sizeof(wav_header)); | ||
257 | rb->close(file_info->outfile); | ||
258 | } | ||
259 | |||
260 | #define INPUT_BUFFER_SIZE (5*8192) | ||
261 | #define OUTPUT_BUFFER_SIZE 8192 /* Must be an integer multiple of 4. */ | ||
262 | |||
263 | unsigned char InputBuffer[INPUT_BUFFER_SIZE+MAD_BUFFER_GUARD]; | ||
264 | unsigned char OutputBuffer[OUTPUT_BUFFER_SIZE]; | ||
265 | unsigned char *OutputPtr=OutputBuffer; | ||
266 | unsigned char *GuardPtr=NULL; | ||
267 | const unsigned char *OutputBufferEnd=OutputBuffer+OUTPUT_BUFFER_SIZE; | ||
268 | |||
269 | /* this is the plugin entry point */ | ||
270 | enum plugin_status plugin_start(struct plugin_api* api, void* file) | ||
271 | { | ||
272 | int i,n,bytesleft; | ||
273 | char s[32]; | ||
274 | unsigned long ticks_taken; | ||
275 | unsigned long start_tick; | ||
276 | unsigned long long speed; | ||
277 | unsigned long xspeed; | ||
278 | long file_ptr; | ||
279 | |||
280 | int Status=0; | ||
281 | unsigned long FrameCount=0; | ||
282 | |||
283 | TEST_PLUGIN_API(api); | ||
284 | |||
285 | rb = api; | ||
286 | |||
287 | mem_ptr=0; | ||
288 | mp3buf=rb->plugin_get_mp3_buffer(&bufsize); | ||
289 | mallocbuf=mp3buf; | ||
290 | filebuf=&mp3buf[MALLOC_BUFSIZE]; | ||
291 | |||
292 | rb->snprintf(s,32,"mp3 bufsize: %d",bufsize); | ||
293 | rb->lcd_putsxy(0,100,s); | ||
294 | rb->lcd_update(); | ||
295 | |||
296 | /* Create a decoder instance */ | ||
297 | |||
298 | mad_stream_init(&Stream); | ||
299 | mad_frame_init(&Frame); | ||
300 | mad_synth_init(&Synth); | ||
301 | mad_timer_reset(&Timer); | ||
302 | |||
303 | //if error: return PLUGIN_ERROR; | ||
304 | |||
305 | file_info.infile=rb->open(file,O_RDONLY); | ||
306 | file_info.outfile=rb->creat("/libmadtest.wav",O_WRONLY); | ||
307 | rb->write(file_info.outfile,wav_header,sizeof(wav_header)); | ||
308 | file_info.curpos=0; | ||
309 | file_info.filesize=rb->filesize(file_info.infile); | ||
310 | |||
311 | if (file_info.filesize > (bufsize-512*1024)) { | ||
312 | rb->close(file_info.infile); | ||
313 | rb->splash(HZ*2, true, "File too large"); | ||
314 | return PLUGIN_ERROR; | ||
315 | } | ||
316 | |||
317 | rb->snprintf(s,32,"Loading file..."); | ||
318 | rb->lcd_putsxy(0,0,s); | ||
319 | rb->lcd_update(); | ||
320 | |||
321 | bytesleft=file_info.filesize; | ||
322 | i=0; | ||
323 | while (bytesleft > 0) { | ||
324 | n=rb->read(file_info.infile,&filebuf[i],bytesleft); | ||
325 | if (n < 0) { | ||
326 | rb->close(file_info.infile); | ||
327 | rb->splash(HZ*2, true, "ERROR READING FILE"); | ||
328 | return PLUGIN_ERROR; | ||
329 | } | ||
330 | n+=i; bytesleft-=n; | ||
331 | } | ||
332 | rb->close(file_info.infile); | ||
333 | |||
334 | mad_stream_init(&Stream); | ||
335 | mad_frame_init(&Frame); | ||
336 | mad_synth_init(&Synth); | ||
337 | mad_timer_reset(&Timer); | ||
338 | |||
339 | file_ptr=0; | ||
340 | start_tick=*(rb->current_tick); | ||
341 | |||
342 | rb->button_clear_queue(); | ||
343 | |||
344 | /* This is the decoding loop. */ | ||
345 | while (file_ptr < file_info.filesize) { | ||
346 | if(Stream.buffer==NULL || Stream.error==MAD_ERROR_BUFLEN) { | ||
347 | size_t ReadSize, Remaining; | ||
348 | unsigned char *ReadStart; | ||
349 | |||
350 | if(Stream.next_frame!=NULL) { | ||
351 | Remaining=Stream.bufend-Stream.next_frame; | ||
352 | memmove(InputBuffer,Stream.next_frame,Remaining); | ||
353 | ReadStart=InputBuffer+Remaining; | ||
354 | ReadSize=INPUT_BUFFER_SIZE-Remaining; | ||
355 | } else { | ||
356 | ReadSize=INPUT_BUFFER_SIZE; | ||
357 | ReadStart=InputBuffer; | ||
358 | Remaining=0; | ||
359 | } | ||
360 | |||
361 | /* Fill-in the buffer. If an error occurs print a message | ||
362 | * and leave the decoding loop. If the end of stream is | ||
363 | * reached we also leave the loop but the return status is | ||
364 | * left untouched. | ||
365 | */ | ||
366 | |||
367 | if ((file_info.filesize-file_ptr) < (int) ReadSize) { | ||
368 | ReadSize=file_info.filesize-file_ptr; | ||
369 | } | ||
370 | memcpy(ReadStart,&filebuf[file_ptr],ReadSize); | ||
371 | file_ptr+=ReadSize; | ||
372 | |||
373 | if (file_ptr >= file_info.filesize) | ||
374 | { | ||
375 | GuardPtr=ReadStart+ReadSize; | ||
376 | memset(GuardPtr,0,MAD_BUFFER_GUARD); | ||
377 | ReadSize+=MAD_BUFFER_GUARD; | ||
378 | } | ||
379 | |||
380 | /* Pipe the new buffer content to libmad's stream decoder | ||
381 | * facility. | ||
382 | */ | ||
383 | mad_stream_buffer(&Stream,InputBuffer,ReadSize+Remaining); | ||
384 | Stream.error=0; | ||
385 | } | ||
386 | |||
387 | if(mad_frame_decode(&Frame,&Stream)) | ||
388 | { | ||
389 | if(MAD_RECOVERABLE(Stream.error)) | ||
390 | { | ||
391 | if(Stream.error!=MAD_ERROR_LOSTSYNC || Stream.this_frame!=GuardPtr) | ||
392 | { | ||
393 | rb->splash(HZ*1, true, "Recoverable...!"); | ||
394 | } | ||
395 | continue; | ||
396 | } | ||
397 | else | ||
398 | if(Stream.error==MAD_ERROR_BUFLEN) | ||
399 | continue; | ||
400 | else | ||
401 | { | ||
402 | rb->splash(HZ*1, true, "Recoverable...!"); | ||
403 | //fprintf(stderr,"%s: unrecoverable frame level error.\n",ProgName); | ||
404 | Status=1; | ||
405 | break; | ||
406 | } | ||
407 | } | ||
408 | |||
409 | /* We assume all frames have same samplerate as the first */ | ||
410 | if(FrameCount==0) { | ||
411 | file_info.samfmt.rate=Frame.header.samplerate; | ||
412 | } | ||
413 | |||
414 | FrameCount++; | ||
415 | /* ?? Do we need the timer module? */ | ||
416 | mad_timer_add(&Timer,Frame.header.duration); | ||
417 | |||
418 | /* DAVE: This can be used to attenuate the audio */ | ||
419 | // if(DoFilter) | ||
420 | // ApplyFilter(&Frame); | ||
421 | |||
422 | mad_synth_frame(&Synth,&Frame); | ||
423 | |||
424 | /* Convert MAD's numbers to an array of 16-bit LE signed integers */ | ||
425 | for(i=0;i<Synth.pcm.length;i++) | ||
426 | { | ||
427 | unsigned short Sample; | ||
428 | |||
429 | /* Left channel */ | ||
430 | Sample=scale(Synth.pcm.samples[0][i],&d0); | ||
431 | *(OutputPtr++)=Sample&0xff; | ||
432 | *(OutputPtr++)=Sample>>8; | ||
433 | |||
434 | /* Right channel. If the decoded stream is monophonic then | ||
435 | * the right output channel is the same as the left one. | ||
436 | */ | ||
437 | if(MAD_NCHANNELS(&Frame.header)==2) | ||
438 | Sample=scale(Synth.pcm.samples[1][i],&d1); | ||
439 | *(OutputPtr++)=Sample&0xff; | ||
440 | *(OutputPtr++)=Sample>>8; | ||
441 | |||
442 | /* Flush the buffer if it is full. */ | ||
443 | if(OutputPtr==OutputBufferEnd) | ||
444 | { | ||
445 | rb->write(file_info.outfile,OutputBuffer,OUTPUT_BUFFER_SIZE); | ||
446 | OutputPtr=OutputBuffer; | ||
447 | } | ||
448 | } | ||
449 | |||
450 | rb->snprintf(s,32,"Bytes Read: %d ",file_ptr); | ||
451 | rb->lcd_putsxy(0,0,s); | ||
452 | |||
453 | rb->snprintf(s,32,"Samples Decoded: %d",file_info.current_sample); | ||
454 | rb->lcd_putsxy(0,20,s); | ||
455 | rb->snprintf(s,32,"Frames Decoded: %d",FrameCount); | ||
456 | rb->lcd_putsxy(0,40,s); | ||
457 | |||
458 | file_info.current_sample+=Synth.pcm.length; | ||
459 | |||
460 | ticks_taken=*(rb->current_tick)-start_tick; | ||
461 | |||
462 | if (ticks_taken==0) { ticks_taken=1; } // Avoid fp exception. | ||
463 | |||
464 | speed=(100*file_info.current_sample)/file_info.samfmt.rate; | ||
465 | xspeed=(speed*10000)/ticks_taken; | ||
466 | rb->snprintf(s,32,"Speed %ld.%02ld%% Secs: %d",(xspeed/100),(xspeed%100),ticks_taken/100); | ||
467 | rb->lcd_putsxy(0,60,s); | ||
468 | |||
469 | rb->lcd_update(); | ||
470 | if (rb->button_get(false)!=BUTTON_NONE) { | ||
471 | close_wav(&file_info); | ||
472 | return PLUGIN_OK; | ||
473 | } | ||
474 | } | ||
475 | |||
476 | close_wav(&file_info); | ||
477 | rb->splash(HZ*2, true, "FINISHED!"); | ||
478 | |||
479 | return PLUGIN_OK; | ||
480 | } | ||