1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2007 Dave Chapman
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.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
23 /* All swcodec targets have BUTTON_SELECT apart from the H10 and M3 */
25 #if CONFIG_KEYPAD == IRIVER_H10_PAD
26 #define TESTCODEC_EXITBUTTON BUTTON_RIGHT
27 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
28 #define TESTCODEC_EXITBUTTON BUTTON_RC_PLAY
30 #define TESTCODEC_EXITBUTTON BUTTON_SELECT
33 static const struct plugin_api
* rb
;
35 CACHE_FUNCTION_WRAPPERS(rb
)
37 /* Log functions copied from test_disk.c */
39 static int max_line
= 0;
40 static int log_fd
= -1;
41 static char logfilename
[MAX_PATH
];
43 static bool log_init(bool use_logfile
)
47 rb
->lcd_setmargins(0, 0);
48 rb
->lcd_getstringsize("A", NULL
, &h
);
49 max_line
= LCD_HEIGHT
/ h
;
51 rb
->lcd_clear_display();
55 rb
->create_numbered_filename(logfilename
, "/", "test_codec_log_", ".txt",
56 2 IF_CNFN_NUM_(, NULL
));
57 log_fd
= rb
->open(logfilename
, O_RDWR
|O_CREAT
|O_TRUNC
);
64 static void log_text(char *text
, bool advance
)
66 rb
->lcd_puts(0, line
, text
);
70 if (++line
>= max_line
)
73 rb
->fdprintf(log_fd
, "%s\n", text
);
77 static void log_close(void)
93 static void* audiobuf
;
94 static void* codec_mallocbuf
;
95 static size_t audiosize
;
96 static char str
[MAX_PATH
];
98 /* Our local implementation of the codec API */
99 static struct codec_api ci
;
101 struct test_track_info
{
102 struct mp3entry id3
; /* TAG metadata */
103 size_t filesize
; /* File total length */
106 static struct test_track_info track
;
107 static bool taginfo_ready
= true;
109 static volatile unsigned int elapsed
;
110 static volatile bool codec_playing
;
111 struct wavinfo_t wavinfo
;
113 static unsigned char wav_header
[44] =
115 'R','I','F','F', // 0 - ChunkID
116 0,0,0,0, // 4 - ChunkSize (filesize-8)
117 'W','A','V','E', // 8 - Format
118 'f','m','t',' ', // 12 - SubChunkID
119 16,0,0,0, // 16 - SubChunk1ID // 16 for PCM
120 1,0, // 20 - AudioFormat (1=16-bit)
121 0,0, // 22 - NumChannels
122 0,0,0,0, // 24 - SampleRate in Hz
123 0,0,0,0, // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8)
124 0,0, // 32 - BlockAlign (== NumChannels * BitsPerSample/8)
125 16,0, // 34 - BitsPerSample
126 'd','a','t','a', // 36 - Subchunk2ID
127 0,0,0,0 // 40 - Subchunk2Size
130 static inline void int2le32(unsigned char* buf
, int32_t x
)
133 buf
[1] = (x
& 0xff00) >> 8;
134 buf
[2] = (x
& 0xff0000) >> 16;
135 buf
[3] = (x
& 0xff000000) >>24;
138 static inline void int2le24(unsigned char* buf
, int32_t x
)
141 buf
[1] = (x
& 0xff00) >> 8;
142 buf
[2] = (x
& 0xff0000) >> 16;
145 static inline void int2le16(unsigned char* buf
, int16_t x
)
148 buf
[1] = (x
& 0xff00) >> 8;
151 void init_wav(char* filename
)
153 wavinfo
.totalsamples
= 0;
155 wavinfo
.fd
= rb
->creat(filename
);
159 /* Write WAV header - we go back and fill in the details at the end */
160 rb
->write(wavinfo
.fd
, wav_header
, sizeof(wav_header
));
165 void close_wav(void) {
166 int filesize
= rb
->filesize(wavinfo
.fd
);
167 int channels
= (wavinfo
.stereomode
== STEREO_MONO
) ? 1 : 2;
168 int bps
= 16; /* TODO */
170 /* We assume 16-bit, Stereo */
172 rb
->lseek(wavinfo
.fd
,0,SEEK_SET
);
174 int2le32(wav_header
+4, filesize
-8); /* ChunkSize */
176 int2le16(wav_header
+22, channels
);
178 int2le32(wav_header
+24, wavinfo
.samplerate
);
180 int2le32(wav_header
+28, wavinfo
.samplerate
* channels
* (bps
/ 8)); /* ByteRate */
182 int2le16(wav_header
+32, channels
* (bps
/ 8));
184 int2le32(wav_header
+40, filesize
- 44); /* Subchunk2Size */
186 rb
->write(wavinfo
.fd
, wav_header
, sizeof(wav_header
));
188 rb
->close(wavinfo
.fd
);
191 /* Returns buffer to malloc array. Only codeclib should need this. */
192 static void* get_codec_memory(size_t *size
)
194 DEBUGF("get_codec_memory(%d)\n",(int)size
);
196 return codec_mallocbuf
;
200 static bool pcmbuf_insert_null(const void *ch1
, const void *ch2
, int count
)
202 /* Always successful - just discard data */
207 /* Prevent idle poweroff */
208 rb
->reset_poweroff_timer();
213 /* 64KB should be enough */
214 static unsigned char wavbuffer
[64*1024];
216 static inline int32_t clip_sample(int32_t sample
)
218 if ((int16_t)sample
!= sample
)
219 sample
= 0x7fff ^ (sample
>> 31);
226 static bool pcmbuf_insert_wav(const void *ch1
, const void *ch2
, int count
)
228 const int16_t* data1_16
;
229 const int16_t* data2_16
;
230 const int32_t* data1_32
;
231 const int32_t* data2_32
;
232 unsigned char* p
= wavbuffer
;
233 const int scale
= wavinfo
.sampledepth
- 15;
234 const int dc_bias
= 1 << (scale
- 1);
236 /* Prevent idle poweroff */
237 rb
->reset_poweroff_timer();
239 if (wavinfo
.sampledepth
<= 16) {
243 switch(wavinfo
.stereomode
)
245 case STEREO_INTERLEAVED
:
247 int2le16(p
,*data1_16
++);
249 int2le16(p
,*data1_16
++);
254 case STEREO_NONINTERLEAVED
:
256 int2le16(p
,*data1_16
++);
258 int2le16(p
,*data2_16
++);
266 int2le16(p
,*data1_16
++);
275 switch(wavinfo
.stereomode
)
277 case STEREO_INTERLEAVED
:
279 int2le16(p
, clip_sample((*data1_32
++ + dc_bias
) >> scale
));
281 int2le16(p
, clip_sample((*data1_32
++ + dc_bias
) >> scale
));
286 case STEREO_NONINTERLEAVED
:
288 int2le16(p
, clip_sample((*data1_32
++ + dc_bias
) >> scale
));
290 int2le16(p
, clip_sample((*data2_32
++ + dc_bias
) >> scale
));
298 int2le16(p
, clip_sample((*data1_32
++ + dc_bias
) >> scale
));
305 wavinfo
.totalsamples
+= count
;
306 rb
->write(wavinfo
.fd
, wavbuffer
, p
- wavbuffer
);
312 /* Set song position in WPS (value in ms). */
313 static void set_elapsed(unsigned int value
)
319 /* Read next <size> amount bytes from file buffer to <ptr>.
320 Will return number of bytes read or 0 if end of file. */
321 static size_t read_filebuf(void *ptr
, size_t size
)
323 if (ci
.curpos
> (off_t
)track
.filesize
)
327 /* TODO: Don't read beyond end of buffer */
328 rb
->memcpy(ptr
, audiobuf
+ ci
.curpos
, size
);
335 /* Request pointer to file buffer which can be used to read
336 <realsize> amount of data. <reqsize> tells the buffer system
337 how much data it should try to allocate. If <realsize> is 0,
338 end of file is reached. */
339 static void* request_buffer(size_t *realsize
, size_t reqsize
)
341 *realsize
= MIN(track
.filesize
-ci
.curpos
,reqsize
);
343 return (audiobuf
+ ci
.curpos
);
347 /* Advance file buffer position by <amount> amount of bytes. */
348 static void advance_buffer(size_t amount
)
354 /* Advance file buffer to a pointer location inside file buffer. */
355 static void advance_buffer_loc(void *ptr
)
357 ci
.curpos
= ptr
- audiobuf
;
361 /* Seek file buffer to position <newpos> beginning of file. */
362 static bool seek_buffer(size_t newpos
)
369 /* Codec should call this function when it has done the seeking. */
370 static void seek_complete(void)
375 /* Request file change from file buffer. Returns true is next
376 track is available and changed. If return value is false,
377 codec should exit immediately with PLUGIN_OK status. */
378 static bool request_next_track(void)
380 /* We are only decoding a single track */
385 /* Free the buffer area of the current codec after its loaded */
386 static void discard_codec(void)
392 static void set_offset(size_t value
)
399 /* Configure different codec buffer parameters. */
400 static void configure(int setting
, intptr_t value
)
404 case DSP_SWITCH_FREQUENCY
:
405 case DSP_SET_FREQUENCY
:
406 DEBUGF("samplerate=%d\n",(int)value
);
407 wavinfo
.samplerate
= (int)value
;
410 case DSP_SET_SAMPLE_DEPTH
:
411 DEBUGF("sampledepth = %d\n",(int)value
);
412 wavinfo
.sampledepth
=(int)value
;
415 case DSP_SET_STEREO_MODE
:
416 DEBUGF("Stereo mode = %d\n",(int)value
);
417 wavinfo
.stereomode
=(int)value
;
423 static void init_ci(void)
425 /* --- Our "fake" implementations of the codec API functions. --- */
427 ci
.get_codec_memory
= get_codec_memory
;
429 if (wavinfo
.fd
>= 0) {
430 ci
.pcmbuf_insert
= pcmbuf_insert_wav
;
432 ci
.pcmbuf_insert
= pcmbuf_insert_null
;
434 ci
.set_elapsed
= set_elapsed
;
435 ci
.read_filebuf
= read_filebuf
;
436 ci
.request_buffer
= request_buffer
;
437 ci
.advance_buffer
= advance_buffer
;
438 ci
.advance_buffer_loc
= advance_buffer_loc
;
439 ci
.seek_buffer
= seek_buffer
;
440 ci
.seek_complete
= seek_complete
;
441 ci
.request_next_track
= request_next_track
;
442 ci
.discard_codec
= discard_codec
;
443 ci
.set_offset
= set_offset
;
444 ci
.configure
= configure
;
446 /* --- "Core" functions --- */
449 ci
.PREFIX(sleep
) = rb
->PREFIX(sleep
);
450 ci
.yield
= rb
->yield
;
452 /* strings and memory */
453 ci
.strcpy
= rb
->strcpy
;
454 ci
.strncpy
= rb
->strncpy
;
455 ci
.strlen
= rb
->strlen
;
456 ci
.strcmp
= rb
->strcmp
;
457 ci
.strcat
= rb
->strcat
;
458 ci
.memset
= rb
->memset
;
459 ci
.memcpy
= rb
->memcpy
;
460 ci
.memmove
= rb
->memmove
;
461 ci
.memcmp
= rb
->memcmp
;
462 ci
.memchr
= rb
->memchr
;
464 #if defined(DEBUG) || defined(SIMULATOR)
465 ci
.debugf
= rb
->debugf
;
467 #ifdef ROCKBOX_HAS_LOGF
471 ci
.qsort
= rb
->qsort
;
472 ci
.global_settings
= rb
->global_settings
;
475 ci
.profile_thread
= rb
->profile_thread
;
476 ci
.profstop
= rb
->profstop
;
477 ci
.profile_func_enter
= rb
->profile_func_enter
;
478 ci
.profile_func_exit
= rb
->profile_func_exit
;
481 #ifdef CACHE_FUNCTIONS_AS_CALL
482 ci
.invalidate_icache
= invalidate_icache
;
483 ci
.flush_icache
= flush_icache
;
487 static void codec_thread(void)
489 const char* codecname
;
492 codecname
= rb
->get_codec_filename(track
.id3
.codectype
);
494 /* Load the codec and start decoding. */
495 res
= rb
->codec_load_file(codecname
,&ci
);
497 /* Signal to the main thread that we are done */
498 codec_playing
= false;
501 static uintptr_t* codec_stack
;
502 static size_t codec_stack_size
;
504 static enum plugin_status
test_track(const char* filename
)
508 enum plugin_status res
= PLUGIN_ERROR
;
509 unsigned long starttick
;
512 unsigned long duration
;
513 struct thread_entry
* codecthread_id
;
516 /* Display filename (excluding any path)*/
517 ch
= rb
->strrchr(filename
, '/');
523 rb
->snprintf(str
,sizeof(str
),"%s",ch
);
526 log_text("Loading...",false);
528 fd
= rb
->open(filename
,O_RDONLY
);
531 log_text("Cannot open file",true);
535 track
.filesize
= rb
->filesize(fd
);
537 /* Clear the id3 struct */
538 rb
->memset(&track
.id3
, 0, sizeof(struct mp3entry
));
540 if (!rb
->get_metadata(&(track
.id3
), fd
, filename
))
542 log_text("Cannot read metadata",true);
546 if (track
.filesize
> audiosize
)
548 log_text("File too large",true);
552 n
= rb
->read(fd
, audiobuf
, track
.filesize
);
554 if (n
!= track
.filesize
)
556 log_text("Read failed.",true);
560 /* Initialise the function pointers in the codec API */
563 /* Prepare the codec struct for playing the whole file */
564 ci
.filesize
= track
.filesize
;
566 ci
.taginfo_ready
= &taginfo_ready
;
568 ci
.stop_codec
= false;
572 starttick
= *rb
->current_tick
;
574 codec_playing
= true;
576 if ((codecthread_id
= rb
->create_thread(codec_thread
,
577 codec_stack
, codec_stack_size
, 0, "testcodec"
578 IF_PRIO(,PRIORITY_PLAYBACK
) IF_COP(, CPU
))) == NULL
)
580 log_text("Cannot create codec thread!",true);
584 /* Wait for codec thread to die */
585 while (codec_playing
)
588 rb
->snprintf(str
,sizeof(str
),"%d of %d",elapsed
,(int)track
.id3
.length
);
591 /* Save the current time before we spin up the disk to access the log */
592 ticks
= *rb
->current_tick
- starttick
;
594 /* Be sure it is done */
595 rb
->thread_wait(codecthread_id
);
601 /* Display benchmark information */
602 rb
->snprintf(str
,sizeof(str
),"Decode time - %d.%02ds",(int)ticks
/100,(int)ticks
%100);
605 duration
= track
.id3
.length
/ 10;
606 rb
->snprintf(str
,sizeof(str
),"File duration - %d.%02ds",(int)duration
/100,(int)duration
%100);
610 speed
= duration
* 10000 / ticks
;
614 rb
->snprintf(str
,sizeof(str
),"%d.%02d%% realtime",(int)speed
/100,(int)speed
%100);
618 /* show effective clockrate in MHz needed for realtime decoding */
621 speed
= CPUFREQ_MAX
/ speed
;
622 rb
->snprintf(str
,sizeof(str
),"%d.%02dMHz needed for realtime",
623 (int)speed
/100,(int)speed
%100);
642 /* plugin entry point */
643 enum plugin_status
plugin_start(const struct plugin_api
* api
, const void* parameter
)
645 uintptr_t* codec_stack_copy
;
646 int result
, selection
= 0;
647 enum plugin_status res
= PLUGIN_OK
;
650 struct dirent
*entry
;
653 char dirpath
[MAX_PATH
];
654 char filename
[MAX_PATH
];
658 if (parameter
== NULL
)
660 rb
->splash(HZ
*2, "No File");
664 codec_mallocbuf
= rb
->plugin_get_audio_buffer(&audiosize
);
667 /* The simulator thread implementation doesn't have stack buffers */
669 codec_stack_size
= 0;
671 /* Borrow the codec thread's stack (in IRAM on most targets) */
673 for (i
= 0; i
< MAXTHREADS
; i
++)
675 if (rb
->strcmp(rb
->threads
[i
].name
,"codec")==0)
677 /* Wait to ensure the codec thread has blocked */
678 while (rb
->threads
[i
].state
!=STATE_BLOCKED
)
681 codec_stack
= rb
->threads
[i
].stack
;
682 codec_stack_size
= rb
->threads
[i
].stack_size
;
687 if (codec_stack
== NULL
)
689 rb
->splash(HZ
*2, "No codec thread!");
694 codec_stack_copy
= codec_mallocbuf
+ 512*1024;
695 audiobuf
= SKIPBYTES(codec_stack_copy
, codec_stack_size
);
696 audiosize
-= 512*1024 + codec_stack_size
;
699 /* Backup the codec thread's stack */
700 rb
->memcpy(codec_stack_copy
,codec_stack
,codec_stack_size
);
703 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
706 rb
->lcd_clear_display();
710 menu
, "test_codec", NULL
,
716 rb
->lcd_clear_display();
718 result
=rb
->do_menu(&menu
,&selection
, NULL
, false);
725 } else if (result
==1) {
729 /* Only create a log file when we are testing a folder */
730 if (!log_init(true)) {
731 rb
->splash(HZ
*2, "Cannot create logfile");
735 } else if (result
==2) {
737 init_wav("/test.wav");
738 if (wavinfo
.fd
< 0) {
739 rb
->splash(HZ
*2, "Cannot create /test.wav");
743 } else if (result
== MENU_ATTACHED_USB
) {
744 res
= PLUGIN_USB_CONNECTED
;
746 } else if (result
< 0) {
752 /* Test all files in the same directory as the file selected by the
755 rb
->strncpy(dirpath
,parameter
,sizeof(dirpath
));
756 ch
= rb
->strrchr(dirpath
,'/');
759 DEBUGF("Scanning directory \"%s\"\n",dirpath
);
760 dir
= rb
->opendir(dirpath
);
762 entry
= rb
->readdir(dir
);
764 if (!(entry
->attribute
& ATTR_DIRECTORY
)) {
765 rb
->snprintf(filename
,sizeof(filename
),"%s%s",dirpath
,entry
->d_name
);
766 test_track(filename
);
770 /* Read next entry */
771 entry
= rb
->readdir(dir
);
777 /* Just test the file */
778 res
= test_track(parameter
);
780 /* Close WAV file (if there was one) */
781 if (wavinfo
.fd
>= 0) {
783 log_text("Wrote /test.wav",true);
786 while (rb
->button_get(true) != TESTCODEC_EXITBUTTON
);
793 /* Restore the codec thread's stack */
794 rb
->memcpy(codec_stack
, codec_stack_copy
, codec_stack_size
);
797 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
798 rb
->cpu_boost(false);