Crossfade: added a new option, rewrote decision logic, updated manual and menus....
[kugel-rb.git] / apps / plugins / test_codec.c
blob6f758639d17813838089fd9742d3ffd54deb7a24
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 == SAMSUNG_YH_PAD
32 #define TESTCODEC_EXITBUTTON BUTTON_PLAY
33 #elif CONFIG_KEYPAD == COWOND2_PAD || CONFIG_KEYPAD == ONDAVX747_PAD
34 #define TESTCODEC_EXITBUTTON BUTTON_POWER
35 #elif defined(HAVE_TOUCHSCREEN)
36 #define TESTCODEC_EXITBUTTON BUTTON_TOPLEFT
37 #else
38 #define TESTCODEC_EXITBUTTON BUTTON_SELECT
39 #endif
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 bool use_dsp;
114 static volatile unsigned int elapsed;
115 static volatile bool codec_playing;
116 static volatile long endtick;
117 struct wavinfo_t wavinfo;
119 static unsigned char wav_header[44] =
121 'R','I','F','F', // 0 - ChunkID
122 0,0,0,0, // 4 - ChunkSize (filesize-8)
123 'W','A','V','E', // 8 - Format
124 'f','m','t',' ', // 12 - SubChunkID
125 16,0,0,0, // 16 - SubChunk1ID // 16 for PCM
126 1,0, // 20 - AudioFormat (1=16-bit)
127 0,0, // 22 - NumChannels
128 0,0,0,0, // 24 - SampleRate in Hz
129 0,0,0,0, // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8)
130 0,0, // 32 - BlockAlign (== NumChannels * BitsPerSample/8)
131 16,0, // 34 - BitsPerSample
132 'd','a','t','a', // 36 - Subchunk2ID
133 0,0,0,0 // 40 - Subchunk2Size
136 static inline void int2le32(unsigned char* buf, int32_t x)
138 buf[0] = (x & 0xff);
139 buf[1] = (x & 0xff00) >> 8;
140 buf[2] = (x & 0xff0000) >> 16;
141 buf[3] = (x & 0xff000000) >>24;
144 static inline void int2le24(unsigned char* buf, int32_t x)
146 buf[0] = (x & 0xff);
147 buf[1] = (x & 0xff00) >> 8;
148 buf[2] = (x & 0xff0000) >> 16;
151 static inline void int2le16(unsigned char* buf, int16_t x)
153 buf[0] = (x & 0xff);
154 buf[1] = (x & 0xff00) >> 8;
157 /* 32KB should be enough */
158 static unsigned char wavbuffer[32*1024];
159 static unsigned char dspbuffer[32*1024];
161 void init_wav(char* filename)
163 wavinfo.totalsamples = 0;
165 wavinfo.fd = rb->creat(filename);
167 if (wavinfo.fd >= 0)
169 /* Write WAV header - we go back and fill in the details at the end */
170 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
175 void close_wav(void) {
176 int filesize = rb->filesize(wavinfo.fd);
177 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
178 int bps = 16; /* TODO */
180 /* We assume 16-bit, Stereo */
182 rb->lseek(wavinfo.fd,0,SEEK_SET);
184 int2le32(wav_header+4, filesize-8); /* ChunkSize */
186 int2le16(wav_header+22, channels);
188 int2le32(wav_header+24, wavinfo.samplerate);
190 int2le32(wav_header+28, wavinfo.samplerate * channels * (bps / 8)); /* ByteRate */
192 int2le16(wav_header+32, channels * (bps / 8));
194 int2le32(wav_header+40, filesize - 44); /* Subchunk2Size */
196 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
198 rb->close(wavinfo.fd);
201 /* Returns buffer to malloc array. Only codeclib should need this. */
202 static void* codec_get_buffer(size_t *size)
204 DEBUGF("codec_get_buffer(%d)\n",(int)size);
205 *size = CODEC_SIZE;
206 return codec_mallocbuf;
209 static int process_dsp(const void *ch1, const void *ch2, int count)
211 const char *src[2] = { ch1, ch2 };
212 int written_count = 0;
213 char *dest = dspbuffer;
215 while (count > 0)
217 int out_count = rb->dsp_output_count(ci.dsp, count);
219 int inp_count = rb->dsp_input_count(ci.dsp, out_count);
221 if (inp_count <= 0)
222 break;
224 if (inp_count > count)
225 inp_count = count;
227 out_count = rb->dsp_process(ci.dsp, dest, src, inp_count);
229 if (out_count <= 0)
230 break;
232 written_count += out_count;
233 dest += out_count * 4;
235 count -= inp_count;
238 return written_count;
241 /* Null output */
242 static bool pcmbuf_insert_null(const void *ch1, const void *ch2, int count)
244 if (use_dsp) process_dsp(ch1, ch2, count);
246 /* Prevent idle poweroff */
247 rb->reset_poweroff_timer();
249 return true;
252 static inline int32_t clip_sample(int32_t sample)
254 if ((int16_t)sample != sample)
255 sample = 0x7fff ^ (sample >> 31);
257 return sample;
261 /* WAV output */
262 static bool pcmbuf_insert_wav(const void *ch1, const void *ch2, int count)
264 const int16_t* data1_16;
265 const int16_t* data2_16;
266 const int32_t* data1_32;
267 const int32_t* data2_32;
268 unsigned char* p = wavbuffer;
269 const int scale = wavinfo.sampledepth - 15;
270 const int dc_bias = 1 << (scale - 1);
271 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
273 /* Prevent idle poweroff */
274 rb->reset_poweroff_timer();
276 if (use_dsp) {
277 count = process_dsp(ch1, ch2, count);
278 wavinfo.totalsamples += count;
279 if (channels == 1)
281 unsigned char *s = dspbuffer, *d = dspbuffer;
282 int c = count;
283 while (c-- > 0)
285 *d++ = *s++;
286 *d++ = *s++;
287 s++;
288 s++;
291 rb->write(wavinfo.fd, dspbuffer, count * 2 * channels);
292 } else {
294 if (wavinfo.sampledepth <= 16) {
295 data1_16 = ch1;
296 data2_16 = ch2;
298 switch(wavinfo.stereomode)
300 case STEREO_INTERLEAVED:
301 while (count--) {
302 int2le16(p,*data1_16++);
303 p += 2;
304 int2le16(p,*data1_16++);
305 p += 2;
307 break;
309 case STEREO_NONINTERLEAVED:
310 while (count--) {
311 int2le16(p,*data1_16++);
312 p += 2;
313 int2le16(p,*data2_16++);
314 p += 2;
317 break;
319 case STEREO_MONO:
320 while (count--) {
321 int2le16(p,*data1_16++);
322 p += 2;
324 break;
326 } else {
327 data1_32 = ch1;
328 data2_32 = ch2;
330 switch(wavinfo.stereomode)
332 case STEREO_INTERLEAVED:
333 while (count--) {
334 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
335 p += 2;
336 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
337 p += 2;
339 break;
341 case STEREO_NONINTERLEAVED:
342 while (count--) {
343 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
344 p += 2;
345 int2le16(p, clip_sample((*data2_32++ + dc_bias) >> scale));
346 p += 2;
349 break;
351 case STEREO_MONO:
352 while (count--) {
353 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
354 p += 2;
356 break;
360 wavinfo.totalsamples += count;
361 rb->write(wavinfo.fd, wavbuffer, p - wavbuffer);
362 } /* else */
364 return true;
367 /* Set song position in WPS (value in ms). */
368 static void set_elapsed(unsigned long value)
370 elapsed = value;
374 /* Read next <size> amount bytes from file buffer to <ptr>.
375 Will return number of bytes read or 0 if end of file. */
376 static size_t read_filebuf(void *ptr, size_t size)
378 if (ci.curpos > (off_t)track.filesize)
380 return 0;
381 } else {
382 /* TODO: Don't read beyond end of buffer */
383 rb->memcpy(ptr, audiobuf + ci.curpos, size);
384 ci.curpos += size;
385 return size;
390 /* Request pointer to file buffer which can be used to read
391 <realsize> amount of data. <reqsize> tells the buffer system
392 how much data it should try to allocate. If <realsize> is 0,
393 end of file is reached. */
394 static void* request_buffer(size_t *realsize, size_t reqsize)
396 *realsize = MIN(track.filesize-ci.curpos,reqsize);
398 return (audiobuf + ci.curpos);
402 /* Advance file buffer position by <amount> amount of bytes. */
403 static void advance_buffer(size_t amount)
405 ci.curpos += amount;
409 /* Advance file buffer to a pointer location inside file buffer. */
410 static void advance_buffer_loc(void *ptr)
412 ci.curpos = ptr - audiobuf;
416 /* Seek file buffer to position <newpos> beginning of file. */
417 static bool seek_buffer(size_t newpos)
419 ci.curpos = newpos;
420 return true;
424 /* Codec should call this function when it has done the seeking. */
425 static void seek_complete(void)
427 /* Do nothing */
430 /* Request file change from file buffer. Returns true is next
431 track is available and changed. If return value is false,
432 codec should exit immediately with PLUGIN_OK status. */
433 static bool request_next_track(void)
435 /* We are only decoding a single track */
436 return false;
440 /* Free the buffer area of the current codec after its loaded */
441 static void discard_codec(void)
443 /* ??? */
447 static void set_offset(size_t value)
449 /* ??? */
450 (void)value;
454 /* Configure different codec buffer parameters. */
455 static void configure(int setting, intptr_t value)
457 if (use_dsp)
458 rb->dsp_configure(ci.dsp, setting, value);
459 switch(setting)
461 case DSP_SWITCH_FREQUENCY:
462 case DSP_SET_FREQUENCY:
463 DEBUGF("samplerate=%d\n",(int)value);
464 wavinfo.samplerate = (int)value;
465 break;
467 case DSP_SET_SAMPLE_DEPTH:
468 DEBUGF("sampledepth = %d\n",(int)value);
469 wavinfo.sampledepth=(int)value;
470 break;
472 case DSP_SET_STEREO_MODE:
473 DEBUGF("Stereo mode = %d\n",(int)value);
474 wavinfo.stereomode=(int)value;
475 break;
480 static void init_ci(void)
482 /* --- Our "fake" implementations of the codec API functions. --- */
484 ci.codec_get_buffer = codec_get_buffer;
486 if (wavinfo.fd >= 0) {
487 ci.pcmbuf_insert = pcmbuf_insert_wav;
488 } else {
489 ci.pcmbuf_insert = pcmbuf_insert_null;
491 ci.set_elapsed = set_elapsed;
492 ci.read_filebuf = read_filebuf;
493 ci.request_buffer = request_buffer;
494 ci.advance_buffer = advance_buffer;
495 ci.advance_buffer_loc = advance_buffer_loc;
496 ci.seek_buffer = seek_buffer;
497 ci.seek_complete = seek_complete;
498 ci.request_next_track = request_next_track;
499 ci.discard_codec = discard_codec;
500 ci.set_offset = set_offset;
501 ci.configure = configure;
502 ci.dsp = (struct dsp_config *)rb->dsp_configure(NULL, DSP_MYDSP,
503 CODEC_IDX_AUDIO);
505 /* --- "Core" functions --- */
507 /* kernel/ system */
508 ci.sleep = rb->sleep;
509 ci.yield = rb->yield;
511 /* strings and memory */
512 ci.strcpy = rb->strcpy;
513 ci.strlen = rb->strlen;
514 ci.strcmp = rb->strcmp;
515 ci.strcat = rb->strcat;
516 ci.memset = rb->memset;
517 ci.memcpy = rb->memcpy;
518 ci.memmove = rb->memmove;
519 ci.memcmp = rb->memcmp;
520 ci.memchr = rb->memchr;
521 ci.strcasestr = rb->strcasestr;
522 #if defined(DEBUG) || defined(SIMULATOR)
523 ci.debugf = rb->debugf;
524 #endif
525 #ifdef ROCKBOX_HAS_LOGF
526 ci.logf = rb->logf;
527 #endif
529 ci.qsort = rb->qsort;
530 ci.global_settings = rb->global_settings;
532 #ifdef RB_PROFILE
533 ci.profile_thread = rb->profile_thread;
534 ci.profstop = rb->profstop;
535 ci.profile_func_enter = rb->profile_func_enter;
536 ci.profile_func_exit = rb->profile_func_exit;
537 #endif
539 #if NUM_CORES > 1
540 ci.cpucache_invalidate = rb->cpucache_invalidate;
541 ci.cpucache_flush = rb->cpucache_flush;
542 #endif
544 #if NUM_CORES > 1
545 ci.create_thread = rb->create_thread;
546 ci.thread_thaw = rb->thread_thaw;
547 ci.thread_wait = rb->thread_wait;
548 ci.semaphore_init = rb->semaphore_init;
549 ci.semaphore_wait = rb->semaphore_wait;
550 ci.semaphore_release = rb->semaphore_release;
551 #endif
553 #ifdef CPU_ARM
554 ci.__div0 = rb->__div0;
555 #endif
558 static void codec_thread(void)
560 const char* codecname;
561 int res;
563 codecname = rb->get_codec_filename(track.id3.codectype);
565 /* Load the codec and start decoding. */
566 res = rb->codec_load_file(codecname,&ci);
568 /* Signal to the main thread that we are done */
569 endtick = *rb->current_tick;
570 codec_playing = false;
573 static enum plugin_status test_track(const char* filename)
575 size_t n;
576 int fd;
577 enum plugin_status res = PLUGIN_ERROR;
578 long starttick;
579 long ticks;
580 unsigned long speed;
581 unsigned long duration;
582 const char* ch;
584 /* Display filename (excluding any path)*/
585 ch = rb->strrchr(filename, '/');
586 if (ch==NULL)
587 ch = filename;
588 else
589 ch++;
591 rb->snprintf(str,sizeof(str),"%s",ch);
592 log_text(str,true);
594 log_text("Loading...",false);
596 fd = rb->open(filename,O_RDONLY);
597 if (fd < 0)
599 log_text("Cannot open file",true);
600 goto exit;
603 track.filesize = rb->filesize(fd);
605 /* Clear the id3 struct */
606 rb->memset(&track.id3, 0, sizeof(struct mp3entry));
608 if (!rb->get_metadata(&(track.id3), fd, filename))
610 log_text("Cannot read metadata",true);
611 goto exit;
614 if (track.filesize > audiosize)
616 log_text("File too large",true);
617 goto exit;
620 n = rb->read(fd, audiobuf, track.filesize);
622 if (n != track.filesize)
624 log_text("Read failed.",true);
625 goto exit;
628 /* Initialise the function pointers in the codec API */
629 init_ci();
631 /* Prepare the codec struct for playing the whole file */
632 ci.filesize = track.filesize;
633 ci.id3 = &track.id3;
634 ci.taginfo_ready = &taginfo_ready;
635 ci.curpos = 0;
636 ci.stop_codec = false;
637 ci.new_track = 0;
638 ci.seek_time = 0;
640 if (use_dsp)
641 rb->dsp_configure(ci.dsp, DSP_RESET, 0);
643 starttick = *rb->current_tick;
645 codec_playing = true;
647 rb->codec_thread_do_callback(codec_thread, NULL);
649 /* Wait for codec thread to die */
650 while (codec_playing)
652 rb->sleep(HZ);
653 rb->snprintf(str,sizeof(str),"%d of %d",elapsed,(int)track.id3.length);
654 log_text(str,false);
656 ticks = endtick - starttick;
658 /* Be sure it is done */
659 rb->codec_thread_do_callback(NULL, NULL);
661 log_text(str,true);
663 if (wavinfo.fd < 0)
665 /* Display benchmark information */
666 rb->snprintf(str,sizeof(str),"Decode time - %d.%02ds",(int)ticks/100,(int)ticks%100);
667 log_text(str,true);
669 duration = track.id3.length / 10;
670 rb->snprintf(str,sizeof(str),"File duration - %d.%02ds",(int)duration/100,(int)duration%100);
671 log_text(str,true);
673 if (ticks > 0)
674 speed = duration * 10000 / ticks;
675 else
676 speed = 0;
678 rb->snprintf(str,sizeof(str),"%d.%02d%% realtime",(int)speed/100,(int)speed%100);
679 log_text(str,true);
681 #ifndef SIMULATOR
682 /* show effective clockrate in MHz needed for realtime decoding */
683 if (speed > 0)
685 speed = CPUFREQ_MAX / speed;
686 rb->snprintf(str,sizeof(str),"%d.%02dMHz needed for realtime",
687 (int)speed/100,(int)speed%100);
688 log_text(str,true);
690 #endif
693 res = PLUGIN_OK;
695 exit:
696 rb->backlight_on();
698 if (fd >= 0)
700 rb->close(fd);
703 return res;
706 /* plugin entry point */
707 enum plugin_status plugin_start(const void* parameter)
709 int result, selection = 0;
710 enum plugin_status res = PLUGIN_OK;
711 int scandir;
712 struct dirent *entry;
713 DIR* dir;
714 char* ch;
715 char dirpath[MAX_PATH];
716 char filename[MAX_PATH];
718 if (parameter == NULL)
720 rb->splash(HZ*2, "No File");
721 return PLUGIN_ERROR;
724 codec_mallocbuf = rb->plugin_get_audio_buffer(&audiosize);
725 audiobuf = SKIPBYTES(codec_mallocbuf, CODEC_SIZE);
726 audiosize -= CODEC_SIZE;
728 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
729 rb->cpu_boost(true);
730 #endif
731 rb->lcd_clear_display();
732 rb->lcd_update();
734 MENUITEM_STRINGLIST(
735 menu, "test_codec", NULL,
736 "Speed test",
737 "Speed test folder",
738 "Write WAV",
739 "Speed test with DSP",
740 "Speed test folder with DSP",
741 "Write WAV with DSP",
742 "Quit",
745 show_menu:
746 rb->lcd_clear_display();
748 result=rb->do_menu(&menu,&selection, NULL, false);
750 if (result == 6)
752 res = PLUGIN_OK;
753 goto exit;
756 scandir = 0;
758 if ((use_dsp = ((result >= 3) && (result <=5)))) {
759 result -= 3;
761 if (result==0) {
762 wavinfo.fd = -1;
763 log_init(false);
764 } else if (result==1) {
765 wavinfo.fd = -1;
766 scandir = 1;
768 /* Only create a log file when we are testing a folder */
769 if (!log_init(true)) {
770 rb->splash(HZ*2, "Cannot create logfile");
771 res = PLUGIN_ERROR;
772 goto exit;
774 } else if (result==2) {
775 log_init(false);
776 init_wav("/test.wav");
777 if (wavinfo.fd < 0) {
778 rb->splash(HZ*2, "Cannot create /test.wav");
779 res = PLUGIN_ERROR;
780 goto exit;
782 } else if (result == MENU_ATTACHED_USB) {
783 res = PLUGIN_USB_CONNECTED;
784 goto exit;
785 } else if (result < 0) {
786 res = PLUGIN_OK;
787 goto exit;
790 if (scandir) {
791 /* Test all files in the same directory as the file selected by the
792 user */
794 rb->strlcpy(dirpath,parameter,sizeof(dirpath));
795 ch = rb->strrchr(dirpath,'/');
796 ch[1]=0;
798 DEBUGF("Scanning directory \"%s\"\n",dirpath);
799 dir = rb->opendir(dirpath);
800 if (dir) {
801 entry = rb->readdir(dir);
802 while (entry) {
803 if (!(entry->attribute & ATTR_DIRECTORY)) {
804 rb->snprintf(filename,sizeof(filename),"%s%s",dirpath,entry->d_name);
805 test_track(filename);
806 log_text("", true);
809 /* Read next entry */
810 entry = rb->readdir(dir);
813 rb->closedir(dir);
815 } else {
816 /* Just test the file */
817 res = test_track(parameter);
819 /* Close WAV file (if there was one) */
820 if (wavinfo.fd >= 0) {
821 close_wav();
822 log_text("Wrote /test.wav",true);
825 while (rb->button_get(true) != TESTCODEC_EXITBUTTON);
827 goto show_menu;
829 exit:
830 log_close();
832 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
833 rb->cpu_boost(false);
834 #endif
836 return res;