FS#10199: Adds limiter DSP function
[kugel-rb.git] / apps / plugins / test_codec.c
blob22516ba37366e43165d939d483d48b2e82e2a85f
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 bool use_dsp;
112 static volatile unsigned int elapsed;
113 static volatile bool codec_playing;
114 static volatile long endtick;
115 struct wavinfo_t wavinfo;
117 static unsigned char wav_header[44] =
119 'R','I','F','F', // 0 - ChunkID
120 0,0,0,0, // 4 - ChunkSize (filesize-8)
121 'W','A','V','E', // 8 - Format
122 'f','m','t',' ', // 12 - SubChunkID
123 16,0,0,0, // 16 - SubChunk1ID // 16 for PCM
124 1,0, // 20 - AudioFormat (1=16-bit)
125 0,0, // 22 - NumChannels
126 0,0,0,0, // 24 - SampleRate in Hz
127 0,0,0,0, // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8)
128 0,0, // 32 - BlockAlign (== NumChannels * BitsPerSample/8)
129 16,0, // 34 - BitsPerSample
130 'd','a','t','a', // 36 - Subchunk2ID
131 0,0,0,0 // 40 - Subchunk2Size
134 static inline void int2le32(unsigned char* buf, int32_t x)
136 buf[0] = (x & 0xff);
137 buf[1] = (x & 0xff00) >> 8;
138 buf[2] = (x & 0xff0000) >> 16;
139 buf[3] = (x & 0xff000000) >>24;
142 static inline void int2le24(unsigned char* buf, int32_t x)
144 buf[0] = (x & 0xff);
145 buf[1] = (x & 0xff00) >> 8;
146 buf[2] = (x & 0xff0000) >> 16;
149 static inline void int2le16(unsigned char* buf, int16_t x)
151 buf[0] = (x & 0xff);
152 buf[1] = (x & 0xff00) >> 8;
155 /* 32KB should be enough */
156 static unsigned char wavbuffer[32*1024];
157 static unsigned char dspbuffer[32*1024];
159 void init_wav(char* filename)
161 wavinfo.totalsamples = 0;
163 wavinfo.fd = rb->creat(filename);
165 if (wavinfo.fd >= 0)
167 /* Write WAV header - we go back and fill in the details at the end */
168 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
173 void close_wav(void) {
174 int filesize = rb->filesize(wavinfo.fd);
175 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
176 int bps = 16; /* TODO */
178 /* We assume 16-bit, Stereo */
180 rb->lseek(wavinfo.fd,0,SEEK_SET);
182 int2le32(wav_header+4, filesize-8); /* ChunkSize */
184 int2le16(wav_header+22, channels);
186 int2le32(wav_header+24, wavinfo.samplerate);
188 int2le32(wav_header+28, wavinfo.samplerate * channels * (bps / 8)); /* ByteRate */
190 int2le16(wav_header+32, channels * (bps / 8));
192 int2le32(wav_header+40, filesize - 44); /* Subchunk2Size */
194 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
196 rb->close(wavinfo.fd);
199 /* Returns buffer to malloc array. Only codeclib should need this. */
200 static void* codec_get_buffer(size_t *size)
202 DEBUGF("codec_get_buffer(%d)\n",(int)size);
203 *size = CODEC_SIZE;
204 return codec_mallocbuf;
207 static int process_dsp(const void *ch1, const void *ch2, int count)
209 const char *src[2] = { ch1, ch2 };
210 int written_count = 0;
211 char *dest = dspbuffer;
213 while (count > 0)
215 int out_count = rb->dsp_output_count(ci.dsp, count);
217 int inp_count = rb->dsp_input_count(ci.dsp, out_count);
219 if (inp_count <= 0)
220 break;
222 if (inp_count > count)
223 inp_count = count;
225 out_count = rb->dsp_process(ci.dsp, dest, src, inp_count);
227 if (out_count <= 0)
228 break;
230 written_count += out_count;
231 dest += out_count * 4;
233 count -= inp_count;
236 return written_count;
239 /* Null output */
240 static bool pcmbuf_insert_null(const void *ch1, const void *ch2, int count)
242 if (use_dsp) process_dsp(ch1, ch2, count);
244 /* Prevent idle poweroff */
245 rb->reset_poweroff_timer();
247 return true;
250 static inline int32_t clip_sample(int32_t sample)
252 if ((int16_t)sample != sample)
253 sample = 0x7fff ^ (sample >> 31);
255 return sample;
259 /* WAV output */
260 static bool pcmbuf_insert_wav(const void *ch1, const void *ch2, int count)
262 const int16_t* data1_16;
263 const int16_t* data2_16;
264 const int32_t* data1_32;
265 const int32_t* data2_32;
266 unsigned char* p = wavbuffer;
267 const int scale = wavinfo.sampledepth - 15;
268 const int dc_bias = 1 << (scale - 1);
269 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
271 /* Prevent idle poweroff */
272 rb->reset_poweroff_timer();
274 if (use_dsp) {
275 count = process_dsp(ch1, ch2, count);
276 wavinfo.totalsamples += count;
277 if (channels == 1)
279 unsigned char *s = dspbuffer, *d = dspbuffer;
280 int c = count;
281 while (c-- > 0)
283 *d++ = *s++;
284 *d++ = *s++;
285 s++;
286 s++;
289 rb->write(wavinfo.fd, dspbuffer, count * 2 * channels);
290 } else {
292 if (wavinfo.sampledepth <= 16) {
293 data1_16 = ch1;
294 data2_16 = ch2;
296 switch(wavinfo.stereomode)
298 case STEREO_INTERLEAVED:
299 while (count--) {
300 int2le16(p,*data1_16++);
301 p += 2;
302 int2le16(p,*data1_16++);
303 p += 2;
305 break;
307 case STEREO_NONINTERLEAVED:
308 while (count--) {
309 int2le16(p,*data1_16++);
310 p += 2;
311 int2le16(p,*data2_16++);
312 p += 2;
315 break;
317 case STEREO_MONO:
318 while (count--) {
319 int2le16(p,*data1_16++);
320 p += 2;
322 break;
324 } else {
325 data1_32 = ch1;
326 data2_32 = ch2;
328 switch(wavinfo.stereomode)
330 case STEREO_INTERLEAVED:
331 while (count--) {
332 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
333 p += 2;
334 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
335 p += 2;
337 break;
339 case STEREO_NONINTERLEAVED:
340 while (count--) {
341 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
342 p += 2;
343 int2le16(p, clip_sample((*data2_32++ + dc_bias) >> scale));
344 p += 2;
347 break;
349 case STEREO_MONO:
350 while (count--) {
351 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
352 p += 2;
354 break;
358 wavinfo.totalsamples += count;
359 rb->write(wavinfo.fd, wavbuffer, p - wavbuffer);
360 } /* else */
362 return true;
365 /* Set song position in WPS (value in ms). */
366 static void set_elapsed(unsigned int value)
368 elapsed = value;
372 /* Read next <size> amount bytes from file buffer to <ptr>.
373 Will return number of bytes read or 0 if end of file. */
374 static size_t read_filebuf(void *ptr, size_t size)
376 if (ci.curpos > (off_t)track.filesize)
378 return 0;
379 } else {
380 /* TODO: Don't read beyond end of buffer */
381 rb->memcpy(ptr, audiobuf + ci.curpos, size);
382 ci.curpos += size;
383 return size;
388 /* Request pointer to file buffer which can be used to read
389 <realsize> amount of data. <reqsize> tells the buffer system
390 how much data it should try to allocate. If <realsize> is 0,
391 end of file is reached. */
392 static void* request_buffer(size_t *realsize, size_t reqsize)
394 *realsize = MIN(track.filesize-ci.curpos,reqsize);
396 return (audiobuf + ci.curpos);
400 /* Advance file buffer position by <amount> amount of bytes. */
401 static void advance_buffer(size_t amount)
403 ci.curpos += amount;
407 /* Advance file buffer to a pointer location inside file buffer. */
408 static void advance_buffer_loc(void *ptr)
410 ci.curpos = ptr - audiobuf;
414 /* Seek file buffer to position <newpos> beginning of file. */
415 static bool seek_buffer(size_t newpos)
417 ci.curpos = newpos;
418 return true;
422 /* Codec should call this function when it has done the seeking. */
423 static void seek_complete(void)
425 /* Do nothing */
428 /* Request file change from file buffer. Returns true is next
429 track is available and changed. If return value is false,
430 codec should exit immediately with PLUGIN_OK status. */
431 static bool request_next_track(void)
433 /* We are only decoding a single track */
434 return false;
438 /* Free the buffer area of the current codec after its loaded */
439 static void discard_codec(void)
441 /* ??? */
445 static void set_offset(size_t value)
447 /* ??? */
448 (void)value;
452 /* Configure different codec buffer parameters. */
453 static void configure(int setting, intptr_t value)
455 if (use_dsp)
456 rb->dsp_configure(ci.dsp, setting, value);
457 switch(setting)
459 case DSP_SWITCH_FREQUENCY:
460 case DSP_SET_FREQUENCY:
461 DEBUGF("samplerate=%d\n",(int)value);
462 wavinfo.samplerate = (int)value;
463 break;
465 case DSP_SET_SAMPLE_DEPTH:
466 DEBUGF("sampledepth = %d\n",(int)value);
467 wavinfo.sampledepth=(int)value;
468 break;
470 case DSP_SET_STEREO_MODE:
471 DEBUGF("Stereo mode = %d\n",(int)value);
472 wavinfo.stereomode=(int)value;
473 break;
478 static void init_ci(void)
480 /* --- Our "fake" implementations of the codec API functions. --- */
482 ci.codec_get_buffer = codec_get_buffer;
484 if (wavinfo.fd >= 0) {
485 ci.pcmbuf_insert = pcmbuf_insert_wav;
486 } else {
487 ci.pcmbuf_insert = pcmbuf_insert_null;
489 ci.set_elapsed = set_elapsed;
490 ci.read_filebuf = read_filebuf;
491 ci.request_buffer = request_buffer;
492 ci.advance_buffer = advance_buffer;
493 ci.advance_buffer_loc = advance_buffer_loc;
494 ci.seek_buffer = seek_buffer;
495 ci.seek_complete = seek_complete;
496 ci.request_next_track = request_next_track;
497 ci.discard_codec = discard_codec;
498 ci.set_offset = set_offset;
499 ci.configure = configure;
500 ci.dsp = (struct dsp_config *)rb->dsp_configure(NULL, DSP_MYDSP,
501 CODEC_IDX_AUDIO);
503 /* --- "Core" functions --- */
505 /* kernel/ system */
506 ci.sleep = rb->sleep;
507 ci.yield = rb->yield;
509 /* strings and memory */
510 ci.strcpy = rb->strcpy;
511 ci.strlen = rb->strlen;
512 ci.strcmp = rb->strcmp;
513 ci.strcat = rb->strcat;
514 ci.memset = rb->memset;
515 ci.memcpy = rb->memcpy;
516 ci.memmove = rb->memmove;
517 ci.memcmp = rb->memcmp;
518 ci.memchr = rb->memchr;
519 ci.strcasestr = rb->strcasestr;
520 #if defined(DEBUG) || defined(SIMULATOR)
521 ci.debugf = rb->debugf;
522 #endif
523 #ifdef ROCKBOX_HAS_LOGF
524 ci.logf = rb->logf;
525 #endif
527 ci.qsort = rb->qsort;
528 ci.global_settings = rb->global_settings;
530 #ifdef RB_PROFILE
531 ci.profile_thread = rb->profile_thread;
532 ci.profstop = rb->profstop;
533 ci.profile_func_enter = rb->profile_func_enter;
534 ci.profile_func_exit = rb->profile_func_exit;
535 #endif
537 #if NUM_CORES > 1
538 ci.cpucache_invalidate = rb->cpucache_invalidate;
539 ci.cpucache_flush = rb->cpucache_flush;
540 #endif
542 #if NUM_CORES > 1
543 ci.create_thread = rb->create_thread;
544 ci.thread_thaw = rb->thread_thaw;
545 ci.thread_wait = rb->thread_wait;
546 ci.semaphore_init = rb->semaphore_init;
547 ci.semaphore_wait = rb->semaphore_wait;
548 ci.semaphore_release = rb->semaphore_release;
549 #endif
551 #ifdef CPU_ARM
552 ci.__div0 = rb->__div0;
553 #endif
556 static void codec_thread(void)
558 const char* codecname;
559 int res;
561 codecname = rb->get_codec_filename(track.id3.codectype);
563 /* Load the codec and start decoding. */
564 res = rb->codec_load_file(codecname,&ci);
566 /* Signal to the main thread that we are done */
567 endtick = *rb->current_tick;
568 codec_playing = false;
571 static enum plugin_status test_track(const char* filename)
573 size_t n;
574 int fd;
575 enum plugin_status res = PLUGIN_ERROR;
576 long starttick;
577 long ticks;
578 unsigned long speed;
579 unsigned long duration;
580 const char* ch;
582 /* Display filename (excluding any path)*/
583 ch = rb->strrchr(filename, '/');
584 if (ch==NULL)
585 ch = filename;
586 else
587 ch++;
589 rb->snprintf(str,sizeof(str),"%s",ch);
590 log_text(str,true);
592 log_text("Loading...",false);
594 fd = rb->open(filename,O_RDONLY);
595 if (fd < 0)
597 log_text("Cannot open file",true);
598 goto exit;
601 track.filesize = rb->filesize(fd);
603 /* Clear the id3 struct */
604 rb->memset(&track.id3, 0, sizeof(struct mp3entry));
606 if (!rb->get_metadata(&(track.id3), fd, filename))
608 log_text("Cannot read metadata",true);
609 goto exit;
612 if (track.filesize > audiosize)
614 log_text("File too large",true);
615 goto exit;
618 n = rb->read(fd, audiobuf, track.filesize);
620 if (n != track.filesize)
622 log_text("Read failed.",true);
623 goto exit;
626 /* Initialise the function pointers in the codec API */
627 init_ci();
629 /* Prepare the codec struct for playing the whole file */
630 ci.filesize = track.filesize;
631 ci.id3 = &track.id3;
632 ci.taginfo_ready = &taginfo_ready;
633 ci.curpos = 0;
634 ci.stop_codec = false;
635 ci.new_track = 0;
636 ci.seek_time = 0;
638 if (use_dsp)
639 rb->dsp_configure(ci.dsp, DSP_RESET, 0);
641 starttick = *rb->current_tick;
643 codec_playing = true;
645 rb->codec_thread_do_callback(codec_thread, NULL);
647 /* Wait for codec thread to die */
648 while (codec_playing)
650 rb->sleep(HZ);
651 rb->snprintf(str,sizeof(str),"%d of %d",elapsed,(int)track.id3.length);
652 log_text(str,false);
654 ticks = endtick - starttick;
656 /* Be sure it is done */
657 rb->codec_thread_do_callback(NULL, NULL);
659 log_text(str,true);
661 if (wavinfo.fd < 0)
663 /* Display benchmark information */
664 rb->snprintf(str,sizeof(str),"Decode time - %d.%02ds",(int)ticks/100,(int)ticks%100);
665 log_text(str,true);
667 duration = track.id3.length / 10;
668 rb->snprintf(str,sizeof(str),"File duration - %d.%02ds",(int)duration/100,(int)duration%100);
669 log_text(str,true);
671 if (ticks > 0)
672 speed = duration * 10000 / ticks;
673 else
674 speed = 0;
676 rb->snprintf(str,sizeof(str),"%d.%02d%% realtime",(int)speed/100,(int)speed%100);
677 log_text(str,true);
679 #ifndef SIMULATOR
680 /* show effective clockrate in MHz needed for realtime decoding */
681 if (speed > 0)
683 speed = CPUFREQ_MAX / speed;
684 rb->snprintf(str,sizeof(str),"%d.%02dMHz needed for realtime",
685 (int)speed/100,(int)speed%100);
686 log_text(str,true);
688 #endif
691 res = PLUGIN_OK;
693 exit:
694 rb->backlight_on();
696 if (fd >= 0)
698 rb->close(fd);
701 return res;
704 /* plugin entry point */
705 enum plugin_status plugin_start(const void* parameter)
707 int result, selection = 0;
708 enum plugin_status res = PLUGIN_OK;
709 int scandir;
710 struct dirent *entry;
711 DIR* dir;
712 char* ch;
713 char dirpath[MAX_PATH];
714 char filename[MAX_PATH];
716 if (parameter == NULL)
718 rb->splash(HZ*2, "No File");
719 return PLUGIN_ERROR;
722 codec_mallocbuf = rb->plugin_get_audio_buffer(&audiosize);
723 audiobuf = SKIPBYTES(codec_mallocbuf, CODEC_SIZE);
724 audiosize -= CODEC_SIZE;
726 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
727 rb->cpu_boost(true);
728 #endif
729 rb->lcd_clear_display();
730 rb->lcd_update();
732 MENUITEM_STRINGLIST(
733 menu, "test_codec", NULL,
734 "Speed test",
735 "Speed test folder",
736 "Write WAV",
737 "Speed test w/DSP",
738 "Speed test folder w/DSP",
739 "Write WAV w/DSP",
740 "Quit",
743 show_menu:
744 rb->lcd_clear_display();
746 result=rb->do_menu(&menu,&selection, NULL, false);
748 if (result == 6)
750 res = PLUGIN_OK;
751 goto exit;
754 scandir = 0;
756 if ((use_dsp = ((result >= 3) && (result <=5)))) {
757 result -= 3;
759 if (result==0) {
760 wavinfo.fd = -1;
761 log_init(false);
762 } else if (result==1) {
763 wavinfo.fd = -1;
764 scandir = 1;
766 /* Only create a log file when we are testing a folder */
767 if (!log_init(true)) {
768 rb->splash(HZ*2, "Cannot create logfile");
769 res = PLUGIN_ERROR;
770 goto exit;
772 } else if (result==2) {
773 log_init(false);
774 init_wav("/test.wav");
775 if (wavinfo.fd < 0) {
776 rb->splash(HZ*2, "Cannot create /test.wav");
777 res = PLUGIN_ERROR;
778 goto exit;
780 } else if (result == MENU_ATTACHED_USB) {
781 res = PLUGIN_USB_CONNECTED;
782 goto exit;
783 } else if (result < 0) {
784 res = PLUGIN_OK;
785 goto exit;
788 if (scandir) {
789 /* Test all files in the same directory as the file selected by the
790 user */
792 rb->strlcpy(dirpath,parameter,sizeof(dirpath));
793 ch = rb->strrchr(dirpath,'/');
794 ch[1]=0;
796 DEBUGF("Scanning directory \"%s\"\n",dirpath);
797 dir = rb->opendir(dirpath);
798 if (dir) {
799 entry = rb->readdir(dir);
800 while (entry) {
801 if (!(entry->attribute & ATTR_DIRECTORY)) {
802 rb->snprintf(filename,sizeof(filename),"%s%s",dirpath,entry->d_name);
803 test_track(filename);
804 log_text("", true);
807 /* Read next entry */
808 entry = rb->readdir(dir);
811 rb->closedir(dir);
813 /* process last samples */
814 if (use_dsp)
815 rb->dsp_flush_limiter_buffer(dspbuffer);
816 } else {
817 /* Just test the file */
818 res = test_track(parameter);
820 /* process last samples */
821 if (use_dsp)
823 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
824 int count = rb->dsp_flush_limiter_buffer(dspbuffer);
825 if (channels == 1)
827 unsigned char *s = dspbuffer, *d = dspbuffer;
828 int c = count;
829 while (c-- > 0)
831 *d++ = *s++;
832 *d++ = *s++;
833 s++;
834 s++;
837 if (wavinfo.fd >= 0)
838 rb->write(wavinfo.fd, dspbuffer, count * 2 * channels);
841 /* Close WAV file (if there was one) */
842 if (wavinfo.fd >= 0) {
843 close_wav();
844 log_text("Wrote /test.wav",true);
847 while (rb->button_get(true) != TESTCODEC_EXITBUTTON);
849 goto show_menu;
851 exit:
852 log_close();
854 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
855 rb->cpu_boost(false);
856 #endif
858 return res;