Introduce __attribute__((unused)) (#defined to UNUSED_ATTR) to mark possibly unused...
[kugel-rb.git] / apps / plugins / test_codec.c
blob5f69f96ad47dbe6152184aabde0f6b452cba8026
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 #else
36 #define TESTCODEC_EXITBUTTON BUTTON_SELECT
37 #endif
39 /* Log functions copied from test_disk.c */
40 static int line = 0;
41 static int max_line = 0;
42 static int log_fd = -1;
43 static char logfilename[MAX_PATH];
45 static bool log_init(bool use_logfile)
47 int h;
49 rb->lcd_getstringsize("A", NULL, &h);
50 max_line = LCD_HEIGHT / h;
51 line = 0;
52 rb->lcd_clear_display();
53 rb->lcd_update();
55 if (use_logfile) {
56 rb->create_numbered_filename(logfilename, "/", "test_codec_log_", ".txt",
57 2 IF_CNFN_NUM_(, NULL));
58 log_fd = rb->open(logfilename, O_RDWR|O_CREAT|O_TRUNC);
59 return log_fd >= 0;
62 return true;
65 static void log_text(char *text, bool advance)
67 rb->lcd_puts(0, line, text);
68 rb->lcd_update();
69 if (advance)
71 if (++line >= max_line)
72 line = 0;
73 if (log_fd >= 0)
74 rb->fdprintf(log_fd, "%s\n", text);
78 static void log_close(void)
80 if (log_fd >= 0)
81 rb->close(log_fd);
84 struct wavinfo_t
86 int fd;
87 int samplerate;
88 int channels;
89 int sampledepth;
90 int stereomode;
91 int totalsamples;
94 static void* audiobuf;
95 static void* codec_mallocbuf;
96 static size_t audiosize;
97 static char str[MAX_PATH];
99 /* Our local implementation of the codec API */
100 static struct codec_api ci;
102 struct test_track_info {
103 struct mp3entry id3; /* TAG metadata */
104 size_t filesize; /* File total length */
107 static struct test_track_info track;
108 static bool taginfo_ready = true;
110 static volatile unsigned int elapsed;
111 static volatile bool codec_playing;
112 static volatile long endtick;
113 struct wavinfo_t wavinfo;
115 static unsigned char wav_header[44] =
117 'R','I','F','F', // 0 - ChunkID
118 0,0,0,0, // 4 - ChunkSize (filesize-8)
119 'W','A','V','E', // 8 - Format
120 'f','m','t',' ', // 12 - SubChunkID
121 16,0,0,0, // 16 - SubChunk1ID // 16 for PCM
122 1,0, // 20 - AudioFormat (1=16-bit)
123 0,0, // 22 - NumChannels
124 0,0,0,0, // 24 - SampleRate in Hz
125 0,0,0,0, // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8)
126 0,0, // 32 - BlockAlign (== NumChannels * BitsPerSample/8)
127 16,0, // 34 - BitsPerSample
128 'd','a','t','a', // 36 - Subchunk2ID
129 0,0,0,0 // 40 - Subchunk2Size
132 static inline void int2le32(unsigned char* buf, int32_t x)
134 buf[0] = (x & 0xff);
135 buf[1] = (x & 0xff00) >> 8;
136 buf[2] = (x & 0xff0000) >> 16;
137 buf[3] = (x & 0xff000000) >>24;
140 static inline void int2le24(unsigned char* buf, int32_t x)
142 buf[0] = (x & 0xff);
143 buf[1] = (x & 0xff00) >> 8;
144 buf[2] = (x & 0xff0000) >> 16;
147 static inline void int2le16(unsigned char* buf, int16_t x)
149 buf[0] = (x & 0xff);
150 buf[1] = (x & 0xff00) >> 8;
153 void init_wav(char* filename)
155 wavinfo.totalsamples = 0;
157 wavinfo.fd = rb->creat(filename);
159 if (wavinfo.fd >= 0)
161 /* Write WAV header - we go back and fill in the details at the end */
162 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
167 void close_wav(void) {
168 int filesize = rb->filesize(wavinfo.fd);
169 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
170 int bps = 16; /* TODO */
172 /* We assume 16-bit, Stereo */
174 rb->lseek(wavinfo.fd,0,SEEK_SET);
176 int2le32(wav_header+4, filesize-8); /* ChunkSize */
178 int2le16(wav_header+22, channels);
180 int2le32(wav_header+24, wavinfo.samplerate);
182 int2le32(wav_header+28, wavinfo.samplerate * channels * (bps / 8)); /* ByteRate */
184 int2le16(wav_header+32, channels * (bps / 8));
186 int2le32(wav_header+40, filesize - 44); /* Subchunk2Size */
188 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
190 rb->close(wavinfo.fd);
193 /* Returns buffer to malloc array. Only codeclib should need this. */
194 static void* codec_get_buffer(size_t *size)
196 DEBUGF("codec_get_buffer(%d)\n",(int)size);
197 *size = CODEC_SIZE;
198 return codec_mallocbuf;
201 /* Null output */
202 static bool pcmbuf_insert_null(const void *ch1, const void *ch2, int count)
204 /* Always successful - just discard data */
205 (void)ch1;
206 (void)ch2;
207 (void)count;
209 /* Prevent idle poweroff */
210 rb->reset_poweroff_timer();
212 return true;
215 /* 64KB should be enough */
216 static unsigned char wavbuffer[64*1024];
218 static inline int32_t clip_sample(int32_t sample)
220 if ((int16_t)sample != sample)
221 sample = 0x7fff ^ (sample >> 31);
223 return sample;
227 /* WAV output */
228 static bool pcmbuf_insert_wav(const void *ch1, const void *ch2, int count)
230 const int16_t* data1_16;
231 const int16_t* data2_16;
232 const int32_t* data1_32;
233 const int32_t* data2_32;
234 unsigned char* p = wavbuffer;
235 const int scale = wavinfo.sampledepth - 15;
236 const int dc_bias = 1 << (scale - 1);
238 /* Prevent idle poweroff */
239 rb->reset_poweroff_timer();
241 if (wavinfo.sampledepth <= 16) {
242 data1_16 = ch1;
243 data2_16 = ch2;
245 switch(wavinfo.stereomode)
247 case STEREO_INTERLEAVED:
248 while (count--) {
249 int2le16(p,*data1_16++);
250 p += 2;
251 int2le16(p,*data1_16++);
252 p += 2;
254 break;
256 case STEREO_NONINTERLEAVED:
257 while (count--) {
258 int2le16(p,*data1_16++);
259 p += 2;
260 int2le16(p,*data2_16++);
261 p += 2;
264 break;
266 case STEREO_MONO:
267 while (count--) {
268 int2le16(p,*data1_16++);
269 p += 2;
271 break;
273 } else {
274 data1_32 = ch1;
275 data2_32 = ch2;
277 switch(wavinfo.stereomode)
279 case STEREO_INTERLEAVED:
280 while (count--) {
281 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
282 p += 2;
283 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
284 p += 2;
286 break;
288 case STEREO_NONINTERLEAVED:
289 while (count--) {
290 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
291 p += 2;
292 int2le16(p, clip_sample((*data2_32++ + dc_bias) >> scale));
293 p += 2;
296 break;
298 case STEREO_MONO:
299 while (count--) {
300 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
301 p += 2;
303 break;
307 wavinfo.totalsamples += count;
308 rb->write(wavinfo.fd, wavbuffer, p - wavbuffer);
310 return true;
314 /* Set song position in WPS (value in ms). */
315 static void set_elapsed(unsigned int value)
317 elapsed = value;
321 /* Read next <size> amount bytes from file buffer to <ptr>.
322 Will return number of bytes read or 0 if end of file. */
323 static size_t read_filebuf(void *ptr, size_t size)
325 if (ci.curpos > (off_t)track.filesize)
327 return 0;
328 } else {
329 /* TODO: Don't read beyond end of buffer */
330 rb->memcpy(ptr, audiobuf + ci.curpos, size);
331 ci.curpos += size;
332 return size;
337 /* Request pointer to file buffer which can be used to read
338 <realsize> amount of data. <reqsize> tells the buffer system
339 how much data it should try to allocate. If <realsize> is 0,
340 end of file is reached. */
341 static void* request_buffer(size_t *realsize, size_t reqsize)
343 *realsize = MIN(track.filesize-ci.curpos,reqsize);
345 return (audiobuf + ci.curpos);
349 /* Advance file buffer position by <amount> amount of bytes. */
350 static void advance_buffer(size_t amount)
352 ci.curpos += amount;
356 /* Advance file buffer to a pointer location inside file buffer. */
357 static void advance_buffer_loc(void *ptr)
359 ci.curpos = ptr - audiobuf;
363 /* Seek file buffer to position <newpos> beginning of file. */
364 static bool seek_buffer(size_t newpos)
366 ci.curpos = newpos;
367 return true;
371 /* Codec should call this function when it has done the seeking. */
372 static void seek_complete(void)
374 /* Do nothing */
377 /* Request file change from file buffer. Returns true is next
378 track is available and changed. If return value is false,
379 codec should exit immediately with PLUGIN_OK status. */
380 static bool request_next_track(void)
382 /* We are only decoding a single track */
383 return false;
387 /* Free the buffer area of the current codec after its loaded */
388 static void discard_codec(void)
390 /* ??? */
394 static void set_offset(size_t value)
396 /* ??? */
397 (void)value;
401 /* Configure different codec buffer parameters. */
402 static void configure(int setting, intptr_t value)
404 switch(setting)
406 case DSP_SWITCH_FREQUENCY:
407 case DSP_SET_FREQUENCY:
408 DEBUGF("samplerate=%d\n",(int)value);
409 wavinfo.samplerate = (int)value;
410 break;
412 case DSP_SET_SAMPLE_DEPTH:
413 DEBUGF("sampledepth = %d\n",(int)value);
414 wavinfo.sampledepth=(int)value;
415 break;
417 case DSP_SET_STEREO_MODE:
418 DEBUGF("Stereo mode = %d\n",(int)value);
419 wavinfo.stereomode=(int)value;
420 break;
425 static void init_ci(void)
427 /* --- Our "fake" implementations of the codec API functions. --- */
429 ci.codec_get_buffer = codec_get_buffer;
431 if (wavinfo.fd >= 0) {
432 ci.pcmbuf_insert = pcmbuf_insert_wav;
433 } else {
434 ci.pcmbuf_insert = pcmbuf_insert_null;
436 ci.set_elapsed = set_elapsed;
437 ci.read_filebuf = read_filebuf;
438 ci.request_buffer = request_buffer;
439 ci.advance_buffer = advance_buffer;
440 ci.advance_buffer_loc = advance_buffer_loc;
441 ci.seek_buffer = seek_buffer;
442 ci.seek_complete = seek_complete;
443 ci.request_next_track = request_next_track;
444 ci.discard_codec = discard_codec;
445 ci.set_offset = set_offset;
446 ci.configure = configure;
448 /* --- "Core" functions --- */
450 /* kernel/ system */
451 ci.sleep = rb->sleep;
452 ci.yield = rb->yield;
454 /* strings and memory */
455 ci.strcpy = rb->strcpy;
456 ci.strlen = rb->strlen;
457 ci.strcmp = rb->strcmp;
458 ci.strcat = rb->strcat;
459 ci.memset = rb->memset;
460 ci.memcpy = rb->memcpy;
461 ci.memmove = rb->memmove;
462 ci.memcmp = rb->memcmp;
463 ci.memchr = rb->memchr;
464 ci.strcasestr = rb->strcasestr;
465 #if defined(DEBUG) || defined(SIMULATOR)
466 ci.debugf = rb->debugf;
467 #endif
468 #ifdef ROCKBOX_HAS_LOGF
469 ci.logf = rb->logf;
470 #endif
472 ci.qsort = rb->qsort;
473 ci.global_settings = rb->global_settings;
475 #ifdef RB_PROFILE
476 ci.profile_thread = rb->profile_thread;
477 ci.profstop = rb->profstop;
478 ci.profile_func_enter = rb->profile_func_enter;
479 ci.profile_func_exit = rb->profile_func_exit;
480 #endif
482 #if NUM_CORES > 1
483 ci.cpucache_invalidate = rb->cpucache_invalidate;
484 ci.cpucache_flush = rb->cpucache_flush;
485 #endif
487 #if NUM_CORES > 1
488 ci.create_thread = rb->create_thread;
489 ci.thread_thaw = rb->thread_thaw;
490 ci.thread_wait = rb->thread_wait;
491 ci.semaphore_init = rb->semaphore_init;
492 ci.semaphore_wait = rb->semaphore_wait;
493 ci.semaphore_release = rb->semaphore_release;
494 #endif
496 #ifdef CPU_ARM
497 ci.__div0 = rb->__div0;
498 #endif
501 static void codec_thread(void)
503 const char* codecname;
504 int res;
506 codecname = rb->get_codec_filename(track.id3.codectype);
508 /* Load the codec and start decoding. */
509 res = rb->codec_load_file(codecname,&ci);
511 /* Signal to the main thread that we are done */
512 endtick = *rb->current_tick;
513 codec_playing = false;
516 static enum plugin_status test_track(const char* filename)
518 size_t n;
519 int fd;
520 enum plugin_status res = PLUGIN_ERROR;
521 long starttick;
522 long ticks;
523 unsigned long speed;
524 unsigned long duration;
525 const char* ch;
527 /* Display filename (excluding any path)*/
528 ch = rb->strrchr(filename, '/');
529 if (ch==NULL)
530 ch = filename;
531 else
532 ch++;
534 rb->snprintf(str,sizeof(str),"%s",ch);
535 log_text(str,true);
537 log_text("Loading...",false);
539 fd = rb->open(filename,O_RDONLY);
540 if (fd < 0)
542 log_text("Cannot open file",true);
543 goto exit;
546 track.filesize = rb->filesize(fd);
548 /* Clear the id3 struct */
549 rb->memset(&track.id3, 0, sizeof(struct mp3entry));
551 if (!rb->get_metadata(&(track.id3), fd, filename))
553 log_text("Cannot read metadata",true);
554 goto exit;
557 if (track.filesize > audiosize)
559 log_text("File too large",true);
560 goto exit;
563 n = rb->read(fd, audiobuf, track.filesize);
565 if (n != track.filesize)
567 log_text("Read failed.",true);
568 goto exit;
571 /* Initialise the function pointers in the codec API */
572 init_ci();
574 /* Prepare the codec struct for playing the whole file */
575 ci.filesize = track.filesize;
576 ci.id3 = &track.id3;
577 ci.taginfo_ready = &taginfo_ready;
578 ci.curpos = 0;
579 ci.stop_codec = false;
580 ci.new_track = 0;
581 ci.seek_time = 0;
583 starttick = *rb->current_tick;
585 codec_playing = true;
587 rb->codec_thread_do_callback(codec_thread, NULL);
589 /* Wait for codec thread to die */
590 while (codec_playing)
592 rb->sleep(HZ);
593 rb->snprintf(str,sizeof(str),"%d of %d",elapsed,(int)track.id3.length);
594 log_text(str,false);
596 ticks = endtick - starttick;
598 /* Be sure it is done */
599 rb->codec_thread_do_callback(NULL, NULL);
601 log_text(str,true);
603 if (wavinfo.fd < 0)
605 /* Display benchmark information */
606 rb->snprintf(str,sizeof(str),"Decode time - %d.%02ds",(int)ticks/100,(int)ticks%100);
607 log_text(str,true);
609 duration = track.id3.length / 10;
610 rb->snprintf(str,sizeof(str),"File duration - %d.%02ds",(int)duration/100,(int)duration%100);
611 log_text(str,true);
613 if (ticks > 0)
614 speed = duration * 10000 / ticks;
615 else
616 speed = 0;
618 rb->snprintf(str,sizeof(str),"%d.%02d%% realtime",(int)speed/100,(int)speed%100);
619 log_text(str,true);
621 #ifndef SIMULATOR
622 /* show effective clockrate in MHz needed for realtime decoding */
623 if (speed > 0)
625 speed = CPUFREQ_MAX / speed;
626 rb->snprintf(str,sizeof(str),"%d.%02dMHz needed for realtime",
627 (int)speed/100,(int)speed%100);
628 log_text(str,true);
630 #endif
633 res = PLUGIN_OK;
635 exit:
636 rb->backlight_on();
638 if (fd >= 0)
640 rb->close(fd);
643 return res;
646 /* plugin entry point */
647 enum plugin_status plugin_start(UNUSED_ATTR const void* parameter)
649 int result, selection = 0;
650 enum plugin_status res = PLUGIN_OK;
651 int scandir;
652 struct dirent *entry;
653 DIR* dir;
654 char* ch;
655 char dirpath[MAX_PATH];
656 char filename[MAX_PATH];
658 if (parameter == NULL)
660 rb->splash(HZ*2, "No File");
661 return PLUGIN_ERROR;
664 codec_mallocbuf = rb->plugin_get_audio_buffer(&audiosize);
665 audiobuf = SKIPBYTES(codec_mallocbuf, CODEC_SIZE);
666 audiosize -= CODEC_SIZE;
668 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
669 rb->cpu_boost(true);
670 #endif
671 rb->lcd_clear_display();
672 rb->lcd_update();
674 MENUITEM_STRINGLIST(
675 menu, "test_codec", NULL,
676 "Speed test",
677 "Speed test folder",
678 "Write WAV",
681 rb->lcd_clear_display();
683 result=rb->do_menu(&menu,&selection, NULL, false);
685 scandir = 0;
687 if (result==0) {
688 wavinfo.fd = -1;
689 log_init(false);
690 } else if (result==1) {
691 wavinfo.fd = -1;
692 scandir = 1;
694 /* Only create a log file when we are testing a folder */
695 if (!log_init(true)) {
696 rb->splash(HZ*2, "Cannot create logfile");
697 res = PLUGIN_ERROR;
698 goto exit;
700 } else if (result==2) {
701 log_init(false);
702 init_wav("/test.wav");
703 if (wavinfo.fd < 0) {
704 rb->splash(HZ*2, "Cannot create /test.wav");
705 res = PLUGIN_ERROR;
706 goto exit;
708 } else if (result == MENU_ATTACHED_USB) {
709 res = PLUGIN_USB_CONNECTED;
710 goto exit;
711 } else if (result < 0) {
712 res = PLUGIN_OK;
713 goto exit;
716 if (scandir) {
717 /* Test all files in the same directory as the file selected by the
718 user */
720 rb->strlcpy(dirpath,parameter,sizeof(dirpath));
721 ch = rb->strrchr(dirpath,'/');
722 ch[1]=0;
724 DEBUGF("Scanning directory \"%s\"\n",dirpath);
725 dir = rb->opendir(dirpath);
726 if (dir) {
727 entry = rb->readdir(dir);
728 while (entry) {
729 if (!(entry->attribute & ATTR_DIRECTORY)) {
730 rb->snprintf(filename,sizeof(filename),"%s%s",dirpath,entry->d_name);
731 test_track(filename);
732 log_text("", true);
735 /* Read next entry */
736 entry = rb->readdir(dir);
739 rb->closedir(dir);
741 } else {
742 /* Just test the file */
743 res = test_track(parameter);
745 /* Close WAV file (if there was one) */
746 if (wavinfo.fd >= 0) {
747 close_wav();
748 log_text("Wrote /test.wav",true);
751 while (rb->button_get(true) != TESTCODEC_EXITBUTTON);
754 exit:
755 log_close();
757 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
758 rb->cpu_boost(false);
759 #endif
761 return res;