New makefile solution: A single invocation of 'make' to build the entire tree. Fully...
[kugel-rb.git] / apps / plugins / test_codec.c
blob3c54610a948aa179e3409169ddae8b58a76d41a2
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Dave Chapman
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include "plugin.h"
23 PLUGIN_HEADER
25 /* All swcodec targets have BUTTON_SELECT apart from the H10 and M3 */
27 #if CONFIG_KEYPAD == IRIVER_H10_PAD
28 #define TESTCODEC_EXITBUTTON BUTTON_RIGHT
29 #elif CONFIG_KEYPAD == IAUDIO_M3_PAD
30 #define TESTCODEC_EXITBUTTON BUTTON_RC_PLAY
31 #elif CONFIG_KEYPAD == COWOND2_PAD
32 #define TESTCODEC_EXITBUTTON BUTTON_POWER
33 #else
34 #define TESTCODEC_EXITBUTTON BUTTON_SELECT
35 #endif
37 static const struct plugin_api* rb;
39 CACHE_FUNCTION_WRAPPERS(rb)
41 /* Log functions copied from test_disk.c */
42 static int line = 0;
43 static int max_line = 0;
44 static int log_fd = -1;
45 static char logfilename[MAX_PATH];
47 static bool log_init(bool use_logfile)
49 int h;
51 rb->lcd_getstringsize("A", NULL, &h);
52 max_line = LCD_HEIGHT / h;
53 line = 0;
54 rb->lcd_clear_display();
55 rb->lcd_update();
57 if (use_logfile) {
58 rb->create_numbered_filename(logfilename, "/", "test_codec_log_", ".txt",
59 2 IF_CNFN_NUM_(, NULL));
60 log_fd = rb->open(logfilename, O_RDWR|O_CREAT|O_TRUNC);
61 return log_fd >= 0;
64 return true;
67 static void log_text(char *text, bool advance)
69 rb->lcd_puts(0, line, text);
70 rb->lcd_update();
71 if (advance)
73 if (++line >= max_line)
74 line = 0;
75 if (log_fd >= 0)
76 rb->fdprintf(log_fd, "%s\n", text);
80 static void log_close(void)
82 if (log_fd >= 0)
83 rb->close(log_fd);
86 struct wavinfo_t
88 int fd;
89 int samplerate;
90 int channels;
91 int sampledepth;
92 int stereomode;
93 int totalsamples;
96 static void* audiobuf;
97 static void* codec_mallocbuf;
98 static size_t audiosize;
99 static char str[MAX_PATH];
101 /* Our local implementation of the codec API */
102 static struct codec_api ci;
104 struct test_track_info {
105 struct mp3entry id3; /* TAG metadata */
106 size_t filesize; /* File total length */
109 static struct test_track_info track;
110 static bool taginfo_ready = true;
112 static volatile unsigned int elapsed;
113 static volatile bool codec_playing;
114 struct wavinfo_t wavinfo;
116 static unsigned char wav_header[44] =
118 'R','I','F','F', // 0 - ChunkID
119 0,0,0,0, // 4 - ChunkSize (filesize-8)
120 'W','A','V','E', // 8 - Format
121 'f','m','t',' ', // 12 - SubChunkID
122 16,0,0,0, // 16 - SubChunk1ID // 16 for PCM
123 1,0, // 20 - AudioFormat (1=16-bit)
124 0,0, // 22 - NumChannels
125 0,0,0,0, // 24 - SampleRate in Hz
126 0,0,0,0, // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8)
127 0,0, // 32 - BlockAlign (== NumChannels * BitsPerSample/8)
128 16,0, // 34 - BitsPerSample
129 'd','a','t','a', // 36 - Subchunk2ID
130 0,0,0,0 // 40 - Subchunk2Size
133 static inline void int2le32(unsigned char* buf, int32_t x)
135 buf[0] = (x & 0xff);
136 buf[1] = (x & 0xff00) >> 8;
137 buf[2] = (x & 0xff0000) >> 16;
138 buf[3] = (x & 0xff000000) >>24;
141 static inline void int2le24(unsigned char* buf, int32_t x)
143 buf[0] = (x & 0xff);
144 buf[1] = (x & 0xff00) >> 8;
145 buf[2] = (x & 0xff0000) >> 16;
148 static inline void int2le16(unsigned char* buf, int16_t x)
150 buf[0] = (x & 0xff);
151 buf[1] = (x & 0xff00) >> 8;
154 void init_wav(char* filename)
156 wavinfo.totalsamples = 0;
158 wavinfo.fd = rb->creat(filename);
160 if (wavinfo.fd >= 0)
162 /* Write WAV header - we go back and fill in the details at the end */
163 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
168 void close_wav(void) {
169 int filesize = rb->filesize(wavinfo.fd);
170 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
171 int bps = 16; /* TODO */
173 /* We assume 16-bit, Stereo */
175 rb->lseek(wavinfo.fd,0,SEEK_SET);
177 int2le32(wav_header+4, filesize-8); /* ChunkSize */
179 int2le16(wav_header+22, channels);
181 int2le32(wav_header+24, wavinfo.samplerate);
183 int2le32(wav_header+28, wavinfo.samplerate * channels * (bps / 8)); /* ByteRate */
185 int2le16(wav_header+32, channels * (bps / 8));
187 int2le32(wav_header+40, filesize - 44); /* Subchunk2Size */
189 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
191 rb->close(wavinfo.fd);
194 /* Returns buffer to malloc array. Only codeclib should need this. */
195 static void* codec_get_buffer(size_t *size)
197 DEBUGF("codec_get_buffer(%d)\n",(int)size);
198 *size = CODEC_SIZE;
199 return codec_mallocbuf;
202 /* Null output */
203 static bool pcmbuf_insert_null(const void *ch1, const void *ch2, int count)
205 /* Always successful - just discard data */
206 (void)ch1;
207 (void)ch2;
208 (void)count;
210 /* Prevent idle poweroff */
211 rb->reset_poweroff_timer();
213 return true;
216 /* 64KB should be enough */
217 static unsigned char wavbuffer[64*1024];
219 static inline int32_t clip_sample(int32_t sample)
221 if ((int16_t)sample != sample)
222 sample = 0x7fff ^ (sample >> 31);
224 return sample;
228 /* WAV output */
229 static bool pcmbuf_insert_wav(const void *ch1, const void *ch2, int count)
231 const int16_t* data1_16;
232 const int16_t* data2_16;
233 const int32_t* data1_32;
234 const int32_t* data2_32;
235 unsigned char* p = wavbuffer;
236 const int scale = wavinfo.sampledepth - 15;
237 const int dc_bias = 1 << (scale - 1);
239 /* Prevent idle poweroff */
240 rb->reset_poweroff_timer();
242 if (wavinfo.sampledepth <= 16) {
243 data1_16 = ch1;
244 data2_16 = ch2;
246 switch(wavinfo.stereomode)
248 case STEREO_INTERLEAVED:
249 while (count--) {
250 int2le16(p,*data1_16++);
251 p += 2;
252 int2le16(p,*data1_16++);
253 p += 2;
255 break;
257 case STEREO_NONINTERLEAVED:
258 while (count--) {
259 int2le16(p,*data1_16++);
260 p += 2;
261 int2le16(p,*data2_16++);
262 p += 2;
265 break;
267 case STEREO_MONO:
268 while (count--) {
269 int2le16(p,*data1_16++);
270 p += 2;
272 break;
274 } else {
275 data1_32 = ch1;
276 data2_32 = ch2;
278 switch(wavinfo.stereomode)
280 case STEREO_INTERLEAVED:
281 while (count--) {
282 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
283 p += 2;
284 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
285 p += 2;
287 break;
289 case STEREO_NONINTERLEAVED:
290 while (count--) {
291 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
292 p += 2;
293 int2le16(p, clip_sample((*data2_32++ + dc_bias) >> scale));
294 p += 2;
297 break;
299 case STEREO_MONO:
300 while (count--) {
301 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
302 p += 2;
304 break;
308 wavinfo.totalsamples += count;
309 rb->write(wavinfo.fd, wavbuffer, p - wavbuffer);
311 return true;
315 /* Set song position in WPS (value in ms). */
316 static void set_elapsed(unsigned int value)
318 elapsed = value;
322 /* Read next <size> amount bytes from file buffer to <ptr>.
323 Will return number of bytes read or 0 if end of file. */
324 static size_t read_filebuf(void *ptr, size_t size)
326 if (ci.curpos > (off_t)track.filesize)
328 return 0;
329 } else {
330 /* TODO: Don't read beyond end of buffer */
331 rb->memcpy(ptr, audiobuf + ci.curpos, size);
332 ci.curpos += size;
333 return size;
338 /* Request pointer to file buffer which can be used to read
339 <realsize> amount of data. <reqsize> tells the buffer system
340 how much data it should try to allocate. If <realsize> is 0,
341 end of file is reached. */
342 static void* request_buffer(size_t *realsize, size_t reqsize)
344 *realsize = MIN(track.filesize-ci.curpos,reqsize);
346 return (audiobuf + ci.curpos);
350 /* Advance file buffer position by <amount> amount of bytes. */
351 static void advance_buffer(size_t amount)
353 ci.curpos += amount;
357 /* Advance file buffer to a pointer location inside file buffer. */
358 static void advance_buffer_loc(void *ptr)
360 ci.curpos = ptr - audiobuf;
364 /* Seek file buffer to position <newpos> beginning of file. */
365 static bool seek_buffer(size_t newpos)
367 ci.curpos = newpos;
368 return true;
372 /* Codec should call this function when it has done the seeking. */
373 static void seek_complete(void)
375 /* Do nothing */
378 /* Request file change from file buffer. Returns true is next
379 track is available and changed. If return value is false,
380 codec should exit immediately with PLUGIN_OK status. */
381 static bool request_next_track(void)
383 /* We are only decoding a single track */
384 return false;
388 /* Free the buffer area of the current codec after its loaded */
389 static void discard_codec(void)
391 /* ??? */
395 static void set_offset(size_t value)
397 /* ??? */
398 (void)value;
402 /* Configure different codec buffer parameters. */
403 static void configure(int setting, intptr_t value)
405 switch(setting)
407 case DSP_SWITCH_FREQUENCY:
408 case DSP_SET_FREQUENCY:
409 DEBUGF("samplerate=%d\n",(int)value);
410 wavinfo.samplerate = (int)value;
411 break;
413 case DSP_SET_SAMPLE_DEPTH:
414 DEBUGF("sampledepth = %d\n",(int)value);
415 wavinfo.sampledepth=(int)value;
416 break;
418 case DSP_SET_STEREO_MODE:
419 DEBUGF("Stereo mode = %d\n",(int)value);
420 wavinfo.stereomode=(int)value;
421 break;
426 static void init_ci(void)
428 /* --- Our "fake" implementations of the codec API functions. --- */
430 ci.codec_get_buffer = codec_get_buffer;
432 if (wavinfo.fd >= 0) {
433 ci.pcmbuf_insert = pcmbuf_insert_wav;
434 } else {
435 ci.pcmbuf_insert = pcmbuf_insert_null;
437 ci.set_elapsed = set_elapsed;
438 ci.read_filebuf = read_filebuf;
439 ci.request_buffer = request_buffer;
440 ci.advance_buffer = advance_buffer;
441 ci.advance_buffer_loc = advance_buffer_loc;
442 ci.seek_buffer = seek_buffer;
443 ci.seek_complete = seek_complete;
444 ci.request_next_track = request_next_track;
445 ci.discard_codec = discard_codec;
446 ci.set_offset = set_offset;
447 ci.configure = configure;
449 /* --- "Core" functions --- */
451 /* kernel/ system */
452 ci.PREFIX(sleep) = rb->PREFIX(sleep);
453 ci.yield = rb->yield;
455 /* strings and memory */
456 ci.strcpy = rb->strcpy;
457 ci.strncpy = rb->strncpy;
458 ci.strlen = rb->strlen;
459 ci.strcmp = rb->strcmp;
460 ci.strcat = rb->strcat;
461 ci.memset = rb->memset;
462 ci.memcpy = rb->memcpy;
463 ci.memmove = rb->memmove;
464 ci.memcmp = rb->memcmp;
465 ci.memchr = rb->memchr;
466 ci.strcasestr = rb->strcasestr;
467 #if defined(DEBUG) || defined(SIMULATOR)
468 ci.debugf = rb->debugf;
469 #endif
470 #ifdef ROCKBOX_HAS_LOGF
471 ci.logf = rb->logf;
472 #endif
474 ci.qsort = rb->qsort;
475 ci.global_settings = rb->global_settings;
477 #ifdef RB_PROFILE
478 ci.profile_thread = rb->profile_thread;
479 ci.profstop = rb->profstop;
480 ci.profile_func_enter = rb->profile_func_enter;
481 ci.profile_func_exit = rb->profile_func_exit;
482 #endif
484 #ifdef CACHE_FUNCTIONS_AS_CALL
485 ci.invalidate_icache = invalidate_icache;
486 ci.flush_icache = flush_icache;
487 #endif
489 #if NUM_CORES > 1
490 ci.create_thread = rb->create_thread;
491 ci.thread_thaw = rb->thread_thaw;
492 ci.thread_wait = rb->thread_wait;
493 ci.semaphore_init = rb->semaphore_init;
494 ci.semaphore_wait = rb->semaphore_wait;
495 ci.semaphore_release = rb->semaphore_release;
496 #endif
500 static void codec_thread(void)
502 const char* codecname;
503 int res;
505 codecname = rb->get_codec_filename(track.id3.codectype);
507 /* Load the codec and start decoding. */
508 res = rb->codec_load_file(codecname,&ci);
510 /* Signal to the main thread that we are done */
511 codec_playing = false;
514 static uintptr_t* codec_stack;
515 static size_t codec_stack_size;
517 static enum plugin_status test_track(const char* filename)
519 size_t n;
520 int fd;
521 enum plugin_status res = PLUGIN_ERROR;
522 unsigned long starttick;
523 unsigned long ticks;
524 unsigned long speed;
525 unsigned long duration;
526 struct thread_entry* codecthread_id;
527 const char* ch;
529 /* Display filename (excluding any path)*/
530 ch = rb->strrchr(filename, '/');
531 if (ch==NULL)
532 ch = filename;
533 else
534 ch++;
536 rb->snprintf(str,sizeof(str),"%s",ch);
537 log_text(str,true);
539 log_text("Loading...",false);
541 fd = rb->open(filename,O_RDONLY);
542 if (fd < 0)
544 log_text("Cannot open file",true);
545 goto exit;
548 track.filesize = rb->filesize(fd);
550 /* Clear the id3 struct */
551 rb->memset(&track.id3, 0, sizeof(struct mp3entry));
553 if (!rb->get_metadata(&(track.id3), fd, filename))
555 log_text("Cannot read metadata",true);
556 goto exit;
559 if (track.filesize > audiosize)
561 log_text("File too large",true);
562 goto exit;
565 n = rb->read(fd, audiobuf, track.filesize);
567 if (n != track.filesize)
569 log_text("Read failed.",true);
570 goto exit;
573 /* Initialise the function pointers in the codec API */
574 init_ci();
576 /* Prepare the codec struct for playing the whole file */
577 ci.filesize = track.filesize;
578 ci.id3 = &track.id3;
579 ci.taginfo_ready = &taginfo_ready;
580 ci.curpos = 0;
581 ci.stop_codec = false;
582 ci.new_track = 0;
583 ci.seek_time = 0;
585 starttick = *rb->current_tick;
587 codec_playing = true;
589 if ((codecthread_id = rb->create_thread(codec_thread,
590 codec_stack, codec_stack_size, 0, "testcodec"
591 IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, CPU))) == NULL)
593 log_text("Cannot create codec thread!",true);
594 goto exit;
597 /* Wait for codec thread to die */
598 while (codec_playing)
600 rb->sleep(HZ);
601 rb->snprintf(str,sizeof(str),"%d of %d",elapsed,(int)track.id3.length);
602 log_text(str,false);
604 /* Save the current time before we spin up the disk to access the log */
605 ticks = *rb->current_tick - starttick;
607 /* Be sure it is done */
608 rb->thread_wait(codecthread_id);
610 log_text(str,true);
612 if (wavinfo.fd < 0)
614 /* Display benchmark information */
615 rb->snprintf(str,sizeof(str),"Decode time - %d.%02ds",(int)ticks/100,(int)ticks%100);
616 log_text(str,true);
618 duration = track.id3.length / 10;
619 rb->snprintf(str,sizeof(str),"File duration - %d.%02ds",(int)duration/100,(int)duration%100);
620 log_text(str,true);
622 if (ticks > 0)
623 speed = duration * 10000 / ticks;
624 else
625 speed = 0;
627 rb->snprintf(str,sizeof(str),"%d.%02d%% realtime",(int)speed/100,(int)speed%100);
628 log_text(str,true);
630 #ifndef SIMULATOR
631 /* show effective clockrate in MHz needed for realtime decoding */
632 if (speed > 0)
634 speed = CPUFREQ_MAX / speed;
635 rb->snprintf(str,sizeof(str),"%d.%02dMHz needed for realtime",
636 (int)speed/100,(int)speed%100);
637 log_text(str,true);
639 #endif
642 res = PLUGIN_OK;
644 exit:
645 rb->backlight_on();
647 if (fd >= 0)
649 rb->close(fd);
652 return res;
655 /* plugin entry point */
656 enum plugin_status plugin_start(const struct plugin_api* api, const void* parameter)
658 uintptr_t* codec_stack_copy;
659 int result, selection = 0;
660 enum plugin_status res = PLUGIN_OK;
661 int scandir;
662 int i;
663 struct dirent *entry;
664 DIR* dir;
665 char* ch;
666 char dirpath[MAX_PATH];
667 char filename[MAX_PATH];
669 rb = api;
671 if (parameter == NULL)
673 rb->splash(HZ*2, "No File");
674 return PLUGIN_ERROR;
677 codec_mallocbuf = rb->plugin_get_audio_buffer(&audiosize);
679 #ifdef SIMULATOR
680 /* The simulator thread implementation doesn't have stack buffers */
681 (void)i;
682 codec_stack_size = 0;
683 #else
684 /* Borrow the codec thread's stack (in IRAM on most targets) */
685 codec_stack = NULL;
686 for (i = 0; i < MAXTHREADS; i++)
688 if (rb->strcmp(rb->threads[i].name,"codec")==0)
690 /* Wait to ensure the codec thread has blocked */
691 while (rb->threads[i].state!=STATE_BLOCKED)
692 rb->yield();
694 codec_stack = rb->threads[i].stack;
695 codec_stack_size = rb->threads[i].stack_size;
696 break;
700 if (codec_stack == NULL)
702 rb->splash(HZ*2, "No codec thread!");
703 return PLUGIN_ERROR;
705 #endif
707 codec_stack_copy = codec_mallocbuf + CODEC_SIZE;
708 audiobuf = SKIPBYTES(codec_stack_copy, codec_stack_size);
709 audiosize -= CODEC_SIZE + codec_stack_size;
711 #ifndef SIMULATOR
712 /* Backup the codec thread's stack */
713 rb->memcpy(codec_stack_copy,codec_stack,codec_stack_size);
714 #endif
716 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
717 rb->cpu_boost(true);
718 #endif
719 rb->lcd_clear_display();
720 rb->lcd_update();
722 MENUITEM_STRINGLIST(
723 menu, "test_codec", NULL,
724 "Speed test",
725 "Speed test folder",
726 "Write WAV",
729 rb->lcd_clear_display();
731 result=rb->do_menu(&menu,&selection, NULL, false);
733 scandir = 0;
735 if (result==0) {
736 wavinfo.fd = -1;
737 log_init(false);
738 } else if (result==1) {
739 wavinfo.fd = -1;
740 scandir = 1;
742 /* Only create a log file when we are testing a folder */
743 if (!log_init(true)) {
744 rb->splash(HZ*2, "Cannot create logfile");
745 res = PLUGIN_ERROR;
746 goto exit;
748 } else if (result==2) {
749 log_init(false);
750 init_wav("/test.wav");
751 if (wavinfo.fd < 0) {
752 rb->splash(HZ*2, "Cannot create /test.wav");
753 res = PLUGIN_ERROR;
754 goto exit;
756 } else if (result == MENU_ATTACHED_USB) {
757 res = PLUGIN_USB_CONNECTED;
758 goto exit;
759 } else if (result < 0) {
760 res = PLUGIN_OK;
761 goto exit;
764 if (scandir) {
765 /* Test all files in the same directory as the file selected by the
766 user */
768 rb->strncpy(dirpath,parameter,sizeof(dirpath));
769 ch = rb->strrchr(dirpath,'/');
770 ch[1]=0;
772 DEBUGF("Scanning directory \"%s\"\n",dirpath);
773 dir = rb->opendir(dirpath);
774 if (dir) {
775 entry = rb->readdir(dir);
776 while (entry) {
777 if (!(entry->attribute & ATTR_DIRECTORY)) {
778 rb->snprintf(filename,sizeof(filename),"%s%s",dirpath,entry->d_name);
779 test_track(filename);
780 log_text("", true);
783 /* Read next entry */
784 entry = rb->readdir(dir);
787 rb->closedir(dir);
789 } else {
790 /* Just test the file */
791 res = test_track(parameter);
793 /* Close WAV file (if there was one) */
794 if (wavinfo.fd >= 0) {
795 close_wav();
796 log_text("Wrote /test.wav",true);
799 while (rb->button_get(true) != TESTCODEC_EXITBUTTON);
802 exit:
803 log_close();
805 #ifndef SIMULATOR
806 /* Restore the codec thread's stack */
807 rb->memcpy(codec_stack, codec_stack_copy, codec_stack_size);
808 #endif
810 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
811 rb->cpu_boost(false);
812 #endif
814 return res;