fix FS#8701 - metronome doesnt change back to ui font after drawing
[Rockbox.git] / apps / plugins / test_codec.c
blob9cbfa706146e07282f175a6a6e4ade4eac46e0e9
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
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 ****************************************************************************/
19 #include "plugin.h"
21 PLUGIN_HEADER
23 /* All swcodec targets have BUTTON_SELECT apart from the H10 */
25 #if CONFIG_KEYPAD == IRIVER_H10_PAD
26 #define TESTCODEC_EXITBUTTON BUTTON_RIGHT
27 #else
28 #define TESTCODEC_EXITBUTTON BUTTON_SELECT
29 #endif
31 static struct plugin_api* rb;
33 CACHE_FUNCTION_WRAPPERS(rb)
35 /* Log functions copied from test_disk.c */
36 static int line = 0;
37 static int max_line = 0;
38 static int log_fd = -1;
39 static char logfilename[MAX_PATH];
41 static bool log_init(bool use_logfile)
43 int h;
45 rb->lcd_setmargins(0, 0);
46 rb->lcd_getstringsize("A", NULL, &h);
47 max_line = LCD_HEIGHT / h;
48 line = 0;
49 rb->lcd_clear_display();
50 rb->lcd_update();
52 if (use_logfile) {
53 rb->create_numbered_filename(logfilename, "/", "test_codec_log_", ".txt",
54 2 IF_CNFN_NUM_(, NULL));
55 log_fd = rb->open(logfilename, O_RDWR|O_CREAT|O_TRUNC);
56 return log_fd >= 0;
59 return true;
62 static void log_text(char *text, bool advance)
64 rb->lcd_puts(0, line, text);
65 rb->lcd_update();
66 if (advance)
68 if (++line >= max_line)
69 line = 0;
70 if (log_fd >= 0)
71 rb->fdprintf(log_fd, "%s\n", text);
75 static void log_close(void)
77 if (log_fd >= 0)
78 rb->close(log_fd);
81 struct wavinfo_t
83 int fd;
84 int samplerate;
85 int channels;
86 int sampledepth;
87 int stereomode;
88 int totalsamples;
91 static void* audiobuf;
92 static void* codec_mallocbuf;
93 static size_t audiosize;
94 static char str[MAX_PATH];
96 /* Our local implementation of the codec API */
97 static struct codec_api ci;
99 struct test_track_info {
100 struct mp3entry id3; /* TAG metadata */
101 size_t filesize; /* File total length */
104 static struct test_track_info track;
105 static bool taginfo_ready = true;
107 static volatile unsigned int elapsed;
108 static volatile bool codec_playing;
109 struct wavinfo_t wavinfo;
111 static unsigned char wav_header[44] =
113 'R','I','F','F', // 0 - ChunkID
114 0,0,0,0, // 4 - ChunkSize (filesize-8)
115 'W','A','V','E', // 8 - Format
116 'f','m','t',' ', // 12 - SubChunkID
117 16,0,0,0, // 16 - SubChunk1ID // 16 for PCM
118 1,0, // 20 - AudioFormat (1=16-bit)
119 0,0, // 22 - NumChannels
120 0,0,0,0, // 24 - SampleRate in Hz
121 0,0,0,0, // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8)
122 0,0, // 32 - BlockAlign (== NumChannels * BitsPerSample/8)
123 16,0, // 34 - BitsPerSample
124 'd','a','t','a', // 36 - Subchunk2ID
125 0,0,0,0 // 40 - Subchunk2Size
128 static inline void int2le32(unsigned char* buf, int32_t x)
130 buf[0] = (x & 0xff);
131 buf[1] = (x & 0xff00) >> 8;
132 buf[2] = (x & 0xff0000) >> 16;
133 buf[3] = (x & 0xff000000) >>24;
136 static inline void int2le24(unsigned char* buf, int32_t x)
138 buf[0] = (x & 0xff);
139 buf[1] = (x & 0xff00) >> 8;
140 buf[2] = (x & 0xff0000) >> 16;
143 static inline void int2le16(unsigned char* buf, int16_t x)
145 buf[0] = (x & 0xff);
146 buf[1] = (x & 0xff00) >> 8;
149 void init_wav(char* filename)
151 wavinfo.totalsamples = 0;
153 wavinfo.fd = rb->creat(filename);
155 if (wavinfo.fd >= 0)
157 /* Write WAV header - we go back and fill in the details at the end */
158 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
163 void close_wav(void) {
164 int filesize = rb->filesize(wavinfo.fd);
165 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
166 int bps = 16; /* TODO */
168 /* We assume 16-bit, Stereo */
170 rb->lseek(wavinfo.fd,0,SEEK_SET);
172 int2le32(wav_header+4, filesize-8); /* ChunkSize */
174 int2le16(wav_header+22, channels);
176 int2le32(wav_header+24, wavinfo.samplerate);
178 int2le32(wav_header+28, wavinfo.samplerate * channels * (bps / 8)); /* ByteRate */
180 int2le16(wav_header+32, channels * (bps / 8));
182 int2le32(wav_header+40, filesize - 44); /* Subchunk2Size */
184 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
186 rb->close(wavinfo.fd);
189 /* Returns buffer to malloc array. Only codeclib should need this. */
190 static void* get_codec_memory(size_t *size)
192 DEBUGF("get_codec_memory(%d)\n",(int)size);
193 *size = 512*1024;
194 return codec_mallocbuf;
197 /* Null output */
198 static bool pcmbuf_insert_null(const void *ch1, const void *ch2, int count)
200 /* Always successful - just discard data */
201 (void)ch1;
202 (void)ch2;
203 (void)count;
205 /* Prevent idle poweroff */
206 rb->reset_poweroff_timer();
208 return true;
211 /* 64KB should be enough */
212 static unsigned char wavbuffer[64*1024];
214 static inline int32_t clip_sample(int32_t sample)
216 if ((int16_t)sample != sample)
217 sample = 0x7fff ^ (sample >> 31);
219 return sample;
223 /* WAV output */
224 static bool pcmbuf_insert_wav(const void *ch1, const void *ch2, int count)
226 const int16_t* data1_16;
227 const int16_t* data2_16;
228 const int32_t* data1_32;
229 const int32_t* data2_32;
230 unsigned char* p = wavbuffer;
231 const int scale = wavinfo.sampledepth - 15;
232 const int dc_bias = 1 << (scale - 1);
234 /* Prevent idle poweroff */
235 rb->reset_poweroff_timer();
237 if (wavinfo.sampledepth <= 16) {
238 data1_16 = ch1;
239 data2_16 = ch2;
241 switch(wavinfo.stereomode)
243 case STEREO_INTERLEAVED:
244 while (count--) {
245 int2le16(p,*data1_16++);
246 p += 2;
247 int2le16(p,*data1_16++);
248 p += 2;
250 break;
252 case STEREO_NONINTERLEAVED:
253 while (count--) {
254 int2le16(p,*data1_16++);
255 p += 2;
256 int2le16(p,*data2_16++);
257 p += 2;
260 break;
262 case STEREO_MONO:
263 while (count--) {
264 int2le16(p,*data1_16++);
265 p += 2;
267 break;
269 } else {
270 data1_32 = ch1;
271 data2_32 = ch2;
273 switch(wavinfo.stereomode)
275 case STEREO_INTERLEAVED:
276 while (count--) {
277 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
278 p += 2;
279 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
280 p += 2;
282 break;
284 case STEREO_NONINTERLEAVED:
285 while (count--) {
286 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
287 p += 2;
288 int2le16(p, clip_sample((*data2_32++ + dc_bias) >> scale));
289 p += 2;
292 break;
294 case STEREO_MONO:
295 while (count--) {
296 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
297 p += 2;
299 break;
303 wavinfo.totalsamples += count;
304 rb->write(wavinfo.fd, wavbuffer, p - wavbuffer);
306 return true;
310 /* Set song position in WPS (value in ms). */
311 static void set_elapsed(unsigned int value)
313 elapsed = value;
317 /* Read next <size> amount bytes from file buffer to <ptr>.
318 Will return number of bytes read or 0 if end of file. */
319 static size_t read_filebuf(void *ptr, size_t size)
321 if (ci.curpos > (off_t)track.filesize)
323 return 0;
324 } else {
325 /* TODO: Don't read beyond end of buffer */
326 rb->memcpy(ptr, audiobuf + ci.curpos, size);
327 ci.curpos += size;
328 return size;
333 /* Request pointer to file buffer which can be used to read
334 <realsize> amount of data. <reqsize> tells the buffer system
335 how much data it should try to allocate. If <realsize> is 0,
336 end of file is reached. */
337 static void* request_buffer(size_t *realsize, size_t reqsize)
339 *realsize = MIN(track.filesize-ci.curpos,reqsize);
341 return (audiobuf + ci.curpos);
345 /* Advance file buffer position by <amount> amount of bytes. */
346 static void advance_buffer(size_t amount)
348 ci.curpos += amount;
352 /* Advance file buffer to a pointer location inside file buffer. */
353 static void advance_buffer_loc(void *ptr)
355 ci.curpos = ptr - audiobuf;
359 /* Seek file buffer to position <newpos> beginning of file. */
360 static bool seek_buffer(size_t newpos)
362 ci.curpos = newpos;
363 return true;
367 /* Codec should call this function when it has done the seeking. */
368 static void seek_complete(void)
370 /* Do nothing */
374 /* Calculate mp3 seek position from given time data in ms. */
375 static off_t mp3_get_filepos(int newtime)
377 /* We don't ask the codec to seek, so no need to implement this. */
378 (void)newtime;
379 return 0;
383 /* Request file change from file buffer. Returns true is next
384 track is available and changed. If return value is false,
385 codec should exit immediately with PLUGIN_OK status. */
386 static bool request_next_track(void)
388 /* We are only decoding a single track */
389 return false;
393 /* Free the buffer area of the current codec after its loaded */
394 static void discard_codec(void)
396 /* ??? */
400 static void set_offset(size_t value)
402 /* ??? */
403 (void)value;
407 /* Configure different codec buffer parameters. */
408 static void configure(int setting, intptr_t value)
410 switch(setting)
412 case DSP_SWITCH_FREQUENCY:
413 case DSP_SET_FREQUENCY:
414 DEBUGF("samplerate=%d\n",(int)value);
415 wavinfo.samplerate = (int)value;
416 break;
418 case DSP_SET_SAMPLE_DEPTH:
419 DEBUGF("sampledepth = %d\n",(int)value);
420 wavinfo.sampledepth=(int)value;
421 break;
423 case DSP_SET_STEREO_MODE:
424 DEBUGF("Stereo mode = %d\n",(int)value);
425 wavinfo.stereomode=(int)value;
426 break;
431 static void init_ci(void)
433 /* --- Our "fake" implementations of the codec API functions. --- */
435 ci.get_codec_memory = get_codec_memory;
437 if (wavinfo.fd >= 0) {
438 ci.pcmbuf_insert = pcmbuf_insert_wav;
439 } else {
440 ci.pcmbuf_insert = pcmbuf_insert_null;
442 ci.set_elapsed = set_elapsed;
443 ci.read_filebuf = read_filebuf;
444 ci.request_buffer = request_buffer;
445 ci.advance_buffer = advance_buffer;
446 ci.advance_buffer_loc = advance_buffer_loc;
447 ci.seek_buffer = seek_buffer;
448 ci.seek_complete = seek_complete;
449 ci.mp3_get_filepos = mp3_get_filepos;
450 ci.request_next_track = request_next_track;
451 ci.discard_codec = discard_codec;
452 ci.set_offset = set_offset;
453 ci.configure = configure;
455 /* --- "Core" functions --- */
457 /* kernel/ system */
458 ci.PREFIX(sleep) = rb->PREFIX(sleep);
459 ci.yield = rb->yield;
461 /* strings and memory */
462 ci.strcpy = rb->strcpy;
463 ci.strncpy = rb->strncpy;
464 ci.strlen = rb->strlen;
465 ci.strcmp = rb->strcmp;
466 ci.strcat = rb->strcat;
467 ci.memset = rb->memset;
468 ci.memcpy = rb->memcpy;
469 ci.memmove = rb->memmove;
470 ci.memcmp = rb->memcmp;
471 ci.memchr = rb->memchr;
473 #if defined(DEBUG) || defined(SIMULATOR)
474 ci.debugf = rb->debugf;
475 #endif
476 #ifdef ROCKBOX_HAS_LOGF
477 ci.logf = rb->logf;
478 #endif
480 ci.qsort = rb->qsort;
481 ci.global_settings = rb->global_settings;
483 #ifdef RB_PROFILE
484 ci.profile_thread = rb->profile_thread;
485 ci.profstop = rb->profstop;
486 ci.profile_func_enter = rb->profile_func_enter;
487 ci.profile_func_exit = rb->profile_func_exit;
488 #endif
490 #ifdef CACHE_FUNCTIONS_AS_CALL
491 ci.invalidate_icache = invalidate_icache;
492 ci.flush_icache = flush_icache;
493 #endif
496 static void codec_thread(void)
498 const char* codecname;
499 int res;
501 codecname = rb->get_codec_filename(track.id3.codectype);
503 /* Load the codec and start decoding. */
504 res = rb->codec_load_file(codecname,&ci);
506 /* Signal to the main thread that we are done */
507 codec_playing = false;
510 static unsigned char* codec_stack;
511 static size_t codec_stack_size;
513 static enum plugin_status test_track(char* filename)
515 size_t n;
516 int fd;
517 enum plugin_status res = PLUGIN_ERROR;
518 unsigned long starttick;
519 unsigned long ticks;
520 unsigned long speed;
521 unsigned long duration;
522 struct thread_entry* codecthread_id;
523 char* ch;
525 /* Display filename (excluding any path)*/
526 ch = rb->strrchr(filename, '/');
527 if (ch==NULL)
528 ch = filename;
529 else
530 ch++;
532 rb->snprintf(str,sizeof(str),"%s",ch);
533 log_text(str,true);
535 log_text("Loading...",false);
537 fd = rb->open(filename,O_RDONLY);
538 if (fd < 0)
540 log_text("Cannot open file",true);
541 goto exit;
544 track.filesize = rb->filesize(fd);
546 /* Clear the id3 struct */
547 rb->memset(&track.id3, 0, sizeof(struct mp3entry));
549 if (!rb->get_metadata(&(track.id3), fd, filename))
551 log_text("Cannot read metadata",true);
552 goto exit;
555 if (track.filesize > audiosize)
557 log_text("File too large",true);
558 goto exit;
561 n = rb->read(fd, audiobuf, track.filesize);
563 if (n != track.filesize)
565 log_text("Read failed.",true);
566 goto exit;
569 /* Initialise the function pointers in the codec API */
570 init_ci();
572 /* Prepare the codec struct for playing the whole file */
573 ci.filesize = track.filesize;
574 ci.id3 = &track.id3;
575 ci.taginfo_ready = &taginfo_ready;
576 ci.curpos = 0;
577 ci.stop_codec = false;
578 ci.new_track = 0;
579 ci.seek_time = 0;
581 starttick = *rb->current_tick;
583 codec_playing = true;
585 if ((codecthread_id = rb->create_thread(codec_thread,
586 (uint8_t*)codec_stack, codec_stack_size, 0, "testcodec"
587 IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, CPU))) == NULL)
589 log_text("Cannot create codec thread!",true);
590 goto exit;
593 /* Wait for codec thread to die */
594 while (codec_playing)
596 rb->sleep(HZ);
597 rb->snprintf(str,sizeof(str),"%d of %d",elapsed,(int)track.id3.length);
598 log_text(str,false);
600 /* Save the current time before we spin up the disk to access the log */
601 ticks = *rb->current_tick - starttick;
603 /* Be sure it is done */
604 rb->thread_wait(codecthread_id);
606 log_text(str,true);
608 if (wavinfo.fd < 0)
610 /* Display benchmark information */
611 rb->snprintf(str,sizeof(str),"Decode time - %d.%02ds",(int)ticks/100,(int)ticks%100);
612 log_text(str,true);
614 duration = track.id3.length / 10;
615 rb->snprintf(str,sizeof(str),"File duration - %d.%02ds",(int)duration/100,(int)duration%100);
616 log_text(str,true);
618 if (ticks > 0)
619 speed = duration * 10000 / ticks;
620 else
621 speed = 0;
623 rb->snprintf(str,sizeof(str),"%d.%02d%% realtime",(int)speed/100,(int)speed%100);
624 log_text(str,true);
627 res = PLUGIN_OK;
629 exit:
630 rb->backlight_on();
632 if (fd >= 0)
634 rb->close(fd);
637 return res;
640 /* plugin entry point */
641 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
643 unsigned char* codec_stack_copy;
644 int result, selection = 0;
645 enum plugin_status res = PLUGIN_OK;
646 int scandir;
647 int i;
648 struct dirent *entry;
649 DIR* dir;
650 char* ch;
651 char dirpath[MAX_PATH];
652 char filename[MAX_PATH];
654 rb = api;
656 if (parameter == NULL)
658 rb->splash(HZ*2, "No File");
659 return PLUGIN_ERROR;
662 codec_mallocbuf = rb->plugin_get_audio_buffer(&audiosize);
664 #ifdef SIMULATOR
665 /* The simulator thread implementation doesn't have stack buffers */
666 (void)i;
667 codec_stack_size = 0;
668 #else
669 /* Borrow the codec thread's stack (in IRAM on most targets) */
670 codec_stack = NULL;
671 for (i = 0; i < MAXTHREADS; i++)
673 if (rb->strcmp(rb->threads[i].name,"codec")==0)
675 /* Wait to ensure the codec thread has blocked */
676 while (rb->threads[i].state!=STATE_BLOCKED)
677 rb->yield();
679 codec_stack = rb->threads[i].stack;
680 codec_stack_size = rb->threads[i].stack_size;
681 break;
685 if (codec_stack == NULL)
687 rb->splash(HZ*2, "No codec thread!");
688 return PLUGIN_ERROR;
690 #endif
692 codec_stack_copy = codec_mallocbuf + 512*1024;
693 audiobuf = codec_stack_copy + codec_stack_size;
694 audiosize -= 512*1024 + codec_stack_size;
696 #ifndef SIMULATOR
697 /* Backup the codec thread's stack */
698 rb->memcpy(codec_stack_copy,codec_stack,codec_stack_size);
699 #endif
701 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
702 rb->cpu_boost(true);
703 #endif
704 rb->lcd_clear_display();
705 rb->lcd_update();
707 MENUITEM_STRINGLIST(
708 menu, "test_codec", NULL,
709 "Speed test",
710 "Speed test folder",
711 "Write WAV",
714 rb->lcd_clear_display();
716 result=rb->do_menu(&menu,&selection);
718 scandir = 0;
720 if (result==0) {
721 wavinfo.fd = -1;
722 log_init(false);
723 } else if (result==1) {
724 wavinfo.fd = -1;
725 scandir = 1;
727 /* Only create a log file when we are testing a folder */
728 if (!log_init(true)) {
729 rb->splash(HZ*2, "Cannot create logfile");
730 res = PLUGIN_ERROR;
731 goto exit;
733 } else if (result==2) {
734 log_init(false);
735 init_wav("/test.wav");
736 if (wavinfo.fd < 0) {
737 rb->splash(HZ*2, "Cannot create /test.wav");
738 res = PLUGIN_ERROR;
739 goto exit;
741 } else if (result == MENU_ATTACHED_USB) {
742 res = PLUGIN_USB_CONNECTED;
743 goto exit;
744 } else if (result < 0) {
745 res = PLUGIN_OK;
746 goto exit;
749 if (scandir) {
750 /* Test all files in the same directory as the file selected by the
751 user */
753 rb->strncpy(dirpath,parameter,sizeof(dirpath));
754 ch = rb->strrchr(dirpath,'/');
755 ch[1]=0;
757 DEBUGF("Scanning directory \"%s\"\n",dirpath);
758 dir = rb->opendir(dirpath);
759 if (dir) {
760 entry = rb->readdir(dir);
761 while (entry) {
762 if (!(entry->attribute & ATTR_DIRECTORY)) {
763 rb->snprintf(filename,sizeof(filename),"%s%s",dirpath,entry->d_name);
764 test_track(filename);
765 log_text("", true);
768 /* Read next entry */
769 entry = rb->readdir(dir);
772 rb->closedir(dir);
774 } else {
775 /* Just test the file */
776 res = test_track(parameter);
778 /* Close WAV file (if there was one) */
779 if (wavinfo.fd >= 0) {
780 close_wav();
781 log_text("Wrote /test.wav",true);
784 while (rb->button_get(true) != TESTCODEC_EXITBUTTON);
787 exit:
788 log_close();
790 #ifndef SIMULATOR
791 /* Restore the codec thread's stack */
792 rb->memcpy(codec_stack, codec_stack_copy, codec_stack_size);
793 #endif
795 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
796 rb->cpu_boost(false);
797 #endif
799 return res;