Fix some quotation marks. Thanks to Alexander Levin for pointing it out.
[Rockbox.git] / apps / plugins / test_codec.c
blob69d905a37e2f4c0efbc0bfc9aafc186c3e849194
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 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
29 #else
30 #define TESTCODEC_EXITBUTTON BUTTON_SELECT
31 #endif
33 static struct plugin_api* rb;
35 CACHE_FUNCTION_WRAPPERS(rb)
37 /* Log functions copied from test_disk.c */
38 static int line = 0;
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)
45 int h;
47 rb->lcd_setmargins(0, 0);
48 rb->lcd_getstringsize("A", NULL, &h);
49 max_line = LCD_HEIGHT / h;
50 line = 0;
51 rb->lcd_clear_display();
52 rb->lcd_update();
54 if (use_logfile) {
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);
58 return log_fd >= 0;
61 return true;
64 static void log_text(char *text, bool advance)
66 rb->lcd_puts(0, line, text);
67 rb->lcd_update();
68 if (advance)
70 if (++line >= max_line)
71 line = 0;
72 if (log_fd >= 0)
73 rb->fdprintf(log_fd, "%s\n", text);
77 static void log_close(void)
79 if (log_fd >= 0)
80 rb->close(log_fd);
83 struct wavinfo_t
85 int fd;
86 int samplerate;
87 int channels;
88 int sampledepth;
89 int stereomode;
90 int totalsamples;
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)
132 buf[0] = (x & 0xff);
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)
140 buf[0] = (x & 0xff);
141 buf[1] = (x & 0xff00) >> 8;
142 buf[2] = (x & 0xff0000) >> 16;
145 static inline void int2le16(unsigned char* buf, int16_t x)
147 buf[0] = (x & 0xff);
148 buf[1] = (x & 0xff00) >> 8;
151 void init_wav(char* filename)
153 wavinfo.totalsamples = 0;
155 wavinfo.fd = rb->creat(filename);
157 if (wavinfo.fd >= 0)
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);
195 *size = 512*1024;
196 return codec_mallocbuf;
199 /* Null output */
200 static bool pcmbuf_insert_null(const void *ch1, const void *ch2, int count)
202 /* Always successful - just discard data */
203 (void)ch1;
204 (void)ch2;
205 (void)count;
207 /* Prevent idle poweroff */
208 rb->reset_poweroff_timer();
210 return true;
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);
221 return sample;
225 /* WAV output */
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) {
240 data1_16 = ch1;
241 data2_16 = ch2;
243 switch(wavinfo.stereomode)
245 case STEREO_INTERLEAVED:
246 while (count--) {
247 int2le16(p,*data1_16++);
248 p += 2;
249 int2le16(p,*data1_16++);
250 p += 2;
252 break;
254 case STEREO_NONINTERLEAVED:
255 while (count--) {
256 int2le16(p,*data1_16++);
257 p += 2;
258 int2le16(p,*data2_16++);
259 p += 2;
262 break;
264 case STEREO_MONO:
265 while (count--) {
266 int2le16(p,*data1_16++);
267 p += 2;
269 break;
271 } else {
272 data1_32 = ch1;
273 data2_32 = ch2;
275 switch(wavinfo.stereomode)
277 case STEREO_INTERLEAVED:
278 while (count--) {
279 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
280 p += 2;
281 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
282 p += 2;
284 break;
286 case STEREO_NONINTERLEAVED:
287 while (count--) {
288 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
289 p += 2;
290 int2le16(p, clip_sample((*data2_32++ + dc_bias) >> scale));
291 p += 2;
294 break;
296 case STEREO_MONO:
297 while (count--) {
298 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
299 p += 2;
301 break;
305 wavinfo.totalsamples += count;
306 rb->write(wavinfo.fd, wavbuffer, p - wavbuffer);
308 return true;
312 /* Set song position in WPS (value in ms). */
313 static void set_elapsed(unsigned int value)
315 elapsed = 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)
325 return 0;
326 } else {
327 /* TODO: Don't read beyond end of buffer */
328 rb->memcpy(ptr, audiobuf + ci.curpos, size);
329 ci.curpos += size;
330 return 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)
350 ci.curpos += 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)
364 ci.curpos = newpos;
365 return true;
369 /* Codec should call this function when it has done the seeking. */
370 static void seek_complete(void)
372 /* Do nothing */
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 */
381 return false;
385 /* Free the buffer area of the current codec after its loaded */
386 static void discard_codec(void)
388 /* ??? */
392 static void set_offset(size_t value)
394 /* ??? */
395 (void)value;
399 /* Configure different codec buffer parameters. */
400 static void configure(int setting, intptr_t value)
402 switch(setting)
404 case DSP_SWITCH_FREQUENCY:
405 case DSP_SET_FREQUENCY:
406 DEBUGF("samplerate=%d\n",(int)value);
407 wavinfo.samplerate = (int)value;
408 break;
410 case DSP_SET_SAMPLE_DEPTH:
411 DEBUGF("sampledepth = %d\n",(int)value);
412 wavinfo.sampledepth=(int)value;
413 break;
415 case DSP_SET_STEREO_MODE:
416 DEBUGF("Stereo mode = %d\n",(int)value);
417 wavinfo.stereomode=(int)value;
418 break;
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;
431 } else {
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 --- */
448 /* kernel/ system */
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;
466 #endif
467 #ifdef ROCKBOX_HAS_LOGF
468 ci.logf = rb->logf;
469 #endif
471 ci.qsort = rb->qsort;
472 ci.global_settings = rb->global_settings;
474 #ifdef RB_PROFILE
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;
479 #endif
481 #ifdef CACHE_FUNCTIONS_AS_CALL
482 ci.invalidate_icache = invalidate_icache;
483 ci.flush_icache = flush_icache;
484 #endif
487 static void codec_thread(void)
489 const char* codecname;
490 int res;
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(char* filename)
506 size_t n;
507 int fd;
508 enum plugin_status res = PLUGIN_ERROR;
509 unsigned long starttick;
510 unsigned long ticks;
511 unsigned long speed;
512 unsigned long duration;
513 struct thread_entry* codecthread_id;
514 char* ch;
516 /* Display filename (excluding any path)*/
517 ch = rb->strrchr(filename, '/');
518 if (ch==NULL)
519 ch = filename;
520 else
521 ch++;
523 rb->snprintf(str,sizeof(str),"%s",ch);
524 log_text(str,true);
526 log_text("Loading...",false);
528 fd = rb->open(filename,O_RDONLY);
529 if (fd < 0)
531 log_text("Cannot open file",true);
532 goto exit;
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);
543 goto exit;
546 if (track.filesize > audiosize)
548 log_text("File too large",true);
549 goto exit;
552 n = rb->read(fd, audiobuf, track.filesize);
554 if (n != track.filesize)
556 log_text("Read failed.",true);
557 goto exit;
560 /* Initialise the function pointers in the codec API */
561 init_ci();
563 /* Prepare the codec struct for playing the whole file */
564 ci.filesize = track.filesize;
565 ci.id3 = &track.id3;
566 ci.taginfo_ready = &taginfo_ready;
567 ci.curpos = 0;
568 ci.stop_codec = false;
569 ci.new_track = 0;
570 ci.seek_time = 0;
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);
581 goto exit;
584 /* Wait for codec thread to die */
585 while (codec_playing)
587 rb->sleep(HZ);
588 rb->snprintf(str,sizeof(str),"%d of %d",elapsed,(int)track.id3.length);
589 log_text(str,false);
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);
597 log_text(str,true);
599 if (wavinfo.fd < 0)
601 /* Display benchmark information */
602 rb->snprintf(str,sizeof(str),"Decode time - %d.%02ds",(int)ticks/100,(int)ticks%100);
603 log_text(str,true);
605 duration = track.id3.length / 10;
606 rb->snprintf(str,sizeof(str),"File duration - %d.%02ds",(int)duration/100,(int)duration%100);
607 log_text(str,true);
609 if (ticks > 0)
610 speed = duration * 10000 / ticks;
611 else
612 speed = 0;
614 rb->snprintf(str,sizeof(str),"%d.%02d%% realtime",(int)speed/100,(int)speed%100);
615 log_text(str,true);
618 res = PLUGIN_OK;
620 exit:
621 rb->backlight_on();
623 if (fd >= 0)
625 rb->close(fd);
628 return res;
631 /* plugin entry point */
632 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
634 uintptr_t* codec_stack_copy;
635 int result, selection = 0;
636 enum plugin_status res = PLUGIN_OK;
637 int scandir;
638 int i;
639 struct dirent *entry;
640 DIR* dir;
641 char* ch;
642 char dirpath[MAX_PATH];
643 char filename[MAX_PATH];
645 rb = api;
647 if (parameter == NULL)
649 rb->splash(HZ*2, "No File");
650 return PLUGIN_ERROR;
653 codec_mallocbuf = rb->plugin_get_audio_buffer(&audiosize);
655 #ifdef SIMULATOR
656 /* The simulator thread implementation doesn't have stack buffers */
657 (void)i;
658 codec_stack_size = 0;
659 #else
660 /* Borrow the codec thread's stack (in IRAM on most targets) */
661 codec_stack = NULL;
662 for (i = 0; i < MAXTHREADS; i++)
664 if (rb->strcmp(rb->threads[i].name,"codec")==0)
666 /* Wait to ensure the codec thread has blocked */
667 while (rb->threads[i].state!=STATE_BLOCKED)
668 rb->yield();
670 codec_stack = rb->threads[i].stack;
671 codec_stack_size = rb->threads[i].stack_size;
672 break;
676 if (codec_stack == NULL)
678 rb->splash(HZ*2, "No codec thread!");
679 return PLUGIN_ERROR;
681 #endif
683 codec_stack_copy = codec_mallocbuf + 512*1024;
684 audiobuf = SKIPBYTES(codec_stack_copy, codec_stack_size);
685 audiosize -= 512*1024 + codec_stack_size;
687 #ifndef SIMULATOR
688 /* Backup the codec thread's stack */
689 rb->memcpy(codec_stack_copy,codec_stack,codec_stack_size);
690 #endif
692 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
693 rb->cpu_boost(true);
694 #endif
695 rb->lcd_clear_display();
696 rb->lcd_update();
698 MENUITEM_STRINGLIST(
699 menu, "test_codec", NULL,
700 "Speed test",
701 "Speed test folder",
702 "Write WAV",
705 rb->lcd_clear_display();
707 result=rb->do_menu(&menu,&selection, NULL, false);
709 scandir = 0;
711 if (result==0) {
712 wavinfo.fd = -1;
713 log_init(false);
714 } else if (result==1) {
715 wavinfo.fd = -1;
716 scandir = 1;
718 /* Only create a log file when we are testing a folder */
719 if (!log_init(true)) {
720 rb->splash(HZ*2, "Cannot create logfile");
721 res = PLUGIN_ERROR;
722 goto exit;
724 } else if (result==2) {
725 log_init(false);
726 init_wav("/test.wav");
727 if (wavinfo.fd < 0) {
728 rb->splash(HZ*2, "Cannot create /test.wav");
729 res = PLUGIN_ERROR;
730 goto exit;
732 } else if (result == MENU_ATTACHED_USB) {
733 res = PLUGIN_USB_CONNECTED;
734 goto exit;
735 } else if (result < 0) {
736 res = PLUGIN_OK;
737 goto exit;
740 if (scandir) {
741 /* Test all files in the same directory as the file selected by the
742 user */
744 rb->strncpy(dirpath,parameter,sizeof(dirpath));
745 ch = rb->strrchr(dirpath,'/');
746 ch[1]=0;
748 DEBUGF("Scanning directory \"%s\"\n",dirpath);
749 dir = rb->opendir(dirpath);
750 if (dir) {
751 entry = rb->readdir(dir);
752 while (entry) {
753 if (!(entry->attribute & ATTR_DIRECTORY)) {
754 rb->snprintf(filename,sizeof(filename),"%s%s",dirpath,entry->d_name);
755 test_track(filename);
756 log_text("", true);
759 /* Read next entry */
760 entry = rb->readdir(dir);
763 rb->closedir(dir);
765 } else {
766 /* Just test the file */
767 res = test_track(parameter);
769 /* Close WAV file (if there was one) */
770 if (wavinfo.fd >= 0) {
771 close_wav();
772 log_text("Wrote /test.wav",true);
775 while (rb->button_get(true) != TESTCODEC_EXITBUTTON);
778 exit:
779 log_close();
781 #ifndef SIMULATOR
782 /* Restore the codec thread's stack */
783 rb->memcpy(codec_stack, codec_stack_copy, codec_stack_size);
784 #endif
786 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
787 rb->cpu_boost(false);
788 #endif
790 return res;