Merge branch 'master' into gsoc-ifdef-cleanup
[kugel-rb.git] / apps / plugins / test_codec.c
blob77ad9c9530ba27be92314d8cb1f79ab30f32f2a5
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 == COWON_D2_PAD || CONFIG_KEYPAD == ONDAVX747_PAD
34 #define TESTCODEC_EXITBUTTON BUTTON_POWER
35 #elif CONFIG_KEYPAD == PBELL_VIBE500_PAD
36 #define TESTCODEC_EXITBUTTON BUTTON_REC
37 #elif defined(HAVE_TOUCHSCREEN)
38 #define TESTCODEC_EXITBUTTON BUTTON_TOPLEFT
39 #else
40 #define TESTCODEC_EXITBUTTON BUTTON_SELECT
41 #endif
43 /* Log functions copied from test_disk.c */
44 static int line = 0;
45 static int max_line = 0;
46 static int log_fd = -1;
47 static char logfilename[MAX_PATH];
49 static void log_close(void)
51 if (log_fd >= 0)
52 rb->close(log_fd);
55 static bool log_init(bool use_logfile)
57 int h;
59 rb->lcd_getstringsize("A", NULL, &h);
60 max_line = LCD_HEIGHT / h;
61 line = 0;
62 rb->lcd_clear_display();
63 rb->lcd_update();
65 if (use_logfile) {
66 log_close();
67 rb->create_numbered_filename(logfilename, "/", "test_codec_log_", ".txt",
68 2 IF_CNFN_NUM_(, NULL));
69 log_fd = rb->open(logfilename, O_RDWR|O_CREAT|O_TRUNC, 0666);
70 return log_fd >= 0;
73 return true;
76 static void log_text(char *text, bool advance)
78 rb->lcd_puts(0, line, text);
79 rb->lcd_update();
80 if (advance)
82 if (++line >= max_line)
83 line = 0;
84 if (log_fd >= 0)
85 rb->fdprintf(log_fd, "%s\n", text);
89 struct wavinfo_t
91 int fd;
92 int samplerate;
93 int channels;
94 int sampledepth;
95 int stereomode;
96 int totalsamples;
99 static void* audiobuf;
100 static void* codec_mallocbuf;
101 static size_t audiosize;
102 static char str[MAX_PATH];
104 /* Our local implementation of the codec API */
105 static struct codec_api ci;
107 struct test_track_info {
108 struct mp3entry id3; /* TAG metadata */
109 size_t filesize; /* File total length */
112 static struct test_track_info track;
113 static bool taginfo_ready = true;
115 static bool use_dsp;
117 static bool checksum;
118 static uint32_t crc32;
120 static volatile unsigned int elapsed;
121 static volatile bool codec_playing;
122 static volatile long endtick;
123 struct wavinfo_t wavinfo;
125 static unsigned char wav_header[44] =
127 'R','I','F','F', // 0 - ChunkID
128 0,0,0,0, // 4 - ChunkSize (filesize-8)
129 'W','A','V','E', // 8 - Format
130 'f','m','t',' ', // 12 - SubChunkID
131 16,0,0,0, // 16 - SubChunk1ID // 16 for PCM
132 1,0, // 20 - AudioFormat (1=16-bit)
133 0,0, // 22 - NumChannels
134 0,0,0,0, // 24 - SampleRate in Hz
135 0,0,0,0, // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8)
136 0,0, // 32 - BlockAlign (== NumChannels * BitsPerSample/8)
137 16,0, // 34 - BitsPerSample
138 'd','a','t','a', // 36 - Subchunk2ID
139 0,0,0,0 // 40 - Subchunk2Size
142 static inline void int2le32(unsigned char* buf, int32_t x)
144 buf[0] = (x & 0xff);
145 buf[1] = (x & 0xff00) >> 8;
146 buf[2] = (x & 0xff0000) >> 16;
147 buf[3] = (x & 0xff000000) >>24;
150 static inline void int2le24(unsigned char* buf, int32_t x)
152 buf[0] = (x & 0xff);
153 buf[1] = (x & 0xff00) >> 8;
154 buf[2] = (x & 0xff0000) >> 16;
157 static inline void int2le16(unsigned char* buf, int16_t x)
159 buf[0] = (x & 0xff);
160 buf[1] = (x & 0xff00) >> 8;
163 /* 32KB should be enough */
164 static unsigned char wavbuffer[32*1024];
165 static unsigned char dspbuffer[32*1024];
167 void init_wav(char* filename)
169 wavinfo.totalsamples = 0;
171 wavinfo.fd = rb->creat(filename, 0666);
173 if (wavinfo.fd >= 0)
175 /* Write WAV header - we go back and fill in the details at the end */
176 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
181 void close_wav(void)
183 int filesize = rb->filesize(wavinfo.fd);
184 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
185 int bps = 16; /* TODO */
187 /* We assume 16-bit, Stereo */
189 rb->lseek(wavinfo.fd,0,SEEK_SET);
191 int2le32(wav_header+4, filesize-8); /* ChunkSize */
193 int2le16(wav_header+22, channels);
195 int2le32(wav_header+24, wavinfo.samplerate);
197 int2le32(wav_header+28, wavinfo.samplerate * channels * (bps / 8)); /* ByteRate */
199 int2le16(wav_header+32, channels * (bps / 8));
201 int2le32(wav_header+40, filesize - 44); /* Subchunk2Size */
203 rb->write(wavinfo.fd, wav_header, sizeof(wav_header));
205 rb->close(wavinfo.fd);
208 /* Returns buffer to malloc array. Only codeclib should need this. */
209 static void* codec_get_buffer(size_t *size)
211 DEBUGF("codec_get_buffer(%d)\n",(int)size);
212 *size = CODEC_SIZE;
213 return codec_mallocbuf;
216 static int process_dsp(const void *ch1, const void *ch2, int count)
218 const char *src[2] = { ch1, ch2 };
219 int written_count = 0;
220 char *dest = dspbuffer;
222 while (count > 0)
224 int out_count = rb->dsp_output_count(ci.dsp, count);
226 int inp_count = rb->dsp_input_count(ci.dsp, out_count);
228 if (inp_count <= 0)
229 break;
231 if (inp_count > count)
232 inp_count = count;
234 out_count = rb->dsp_process(ci.dsp, dest, src, inp_count);
236 if (out_count <= 0)
237 break;
239 written_count += out_count;
240 dest += out_count * 4;
242 count -= inp_count;
245 return written_count;
248 static inline int32_t clip_sample(int32_t sample)
250 if ((int16_t)sample != sample)
251 sample = 0x7fff ^ (sample >> 31);
253 return sample;
256 /* Null output */
257 static void pcmbuf_insert_null(const void *ch1, const void *ch2, int count)
259 if (use_dsp)
260 process_dsp(ch1, ch2, count);
262 /* Prevent idle poweroff */
263 rb->reset_poweroff_timer();
266 /* WAV output or calculate crc32 of output*/
267 static void pcmbuf_insert_wav_checksum(const void *ch1, const void *ch2, int count)
269 const int16_t* data1_16;
270 const int16_t* data2_16;
271 const int32_t* data1_32;
272 const int32_t* data2_32;
273 unsigned char* p = wavbuffer;
274 const int scale = wavinfo.sampledepth - 15;
275 const int dc_bias = 1 << (scale - 1);
276 int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2;
278 /* Prevent idle poweroff */
279 rb->reset_poweroff_timer();
281 if (use_dsp) {
282 count = process_dsp(ch1, ch2, count);
283 wavinfo.totalsamples += count;
284 if (channels == 1)
286 unsigned char *s = dspbuffer, *d = dspbuffer;
287 int c = count;
288 while (c-- > 0)
290 *d++ = *s++;
291 *d++ = *s++;
292 s++;
293 s++;
296 if (checksum)
297 crc32 = rb->crc_32(dspbuffer, count * 2 * channels, crc32);
298 else
299 rb->write(wavinfo.fd, dspbuffer, count * 2 * channels);
301 else
303 if (wavinfo.sampledepth <= 16) {
304 data1_16 = ch1;
305 data2_16 = ch2;
307 switch(wavinfo.stereomode)
309 case STEREO_INTERLEAVED:
310 while (count--) {
311 int2le16(p,*data1_16++);
312 p += 2;
313 int2le16(p,*data1_16++);
314 p += 2;
316 break;
318 case STEREO_NONINTERLEAVED:
319 while (count--) {
320 int2le16(p,*data1_16++);
321 p += 2;
322 int2le16(p,*data2_16++);
323 p += 2;
326 break;
328 case STEREO_MONO:
329 while (count--) {
330 int2le16(p,*data1_16++);
331 p += 2;
333 break;
335 } else {
336 data1_32 = ch1;
337 data2_32 = ch2;
339 switch(wavinfo.stereomode)
341 case STEREO_INTERLEAVED:
342 while (count--) {
343 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
344 p += 2;
345 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
346 p += 2;
348 break;
350 case STEREO_NONINTERLEAVED:
351 while (count--) {
352 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
353 p += 2;
354 int2le16(p, clip_sample((*data2_32++ + dc_bias) >> scale));
355 p += 2;
358 break;
360 case STEREO_MONO:
361 while (count--) {
362 int2le16(p, clip_sample((*data1_32++ + dc_bias) >> scale));
363 p += 2;
365 break;
369 wavinfo.totalsamples += count;
370 if (checksum)
371 crc32 = rb->crc_32(wavbuffer, p - wavbuffer, crc32);
372 else
373 rb->write(wavinfo.fd, wavbuffer, p - wavbuffer);
374 } /* else */
377 /* Set song position in WPS (value in ms). */
378 static void set_elapsed(unsigned long value)
380 elapsed = value;
384 /* Read next <size> amount bytes from file buffer to <ptr>.
385 Will return number of bytes read or 0 if end of file. */
386 static size_t read_filebuf(void *ptr, size_t size)
388 if (ci.curpos > (off_t)track.filesize)
390 return 0;
391 } else {
392 /* TODO: Don't read beyond end of buffer */
393 rb->memcpy(ptr, audiobuf + ci.curpos, size);
394 ci.curpos += size;
395 return size;
400 /* Request pointer to file buffer which can be used to read
401 <realsize> amount of data. <reqsize> tells the buffer system
402 how much data it should try to allocate. If <realsize> is 0,
403 end of file is reached. */
404 static void* request_buffer(size_t *realsize, size_t reqsize)
406 *realsize = MIN(track.filesize-ci.curpos,reqsize);
408 return (audiobuf + ci.curpos);
412 /* Advance file buffer position by <amount> amount of bytes. */
413 static void advance_buffer(size_t amount)
415 ci.curpos += amount;
419 /* Advance file buffer to a pointer location inside file buffer. */
420 static void advance_buffer_loc(void *ptr)
422 ci.curpos = ptr - audiobuf;
426 /* Seek file buffer to position <newpos> beginning of file. */
427 static bool seek_buffer(size_t newpos)
429 ci.curpos = newpos;
430 return true;
434 /* Codec should call this function when it has done the seeking. */
435 static void seek_complete(void)
437 /* Do nothing */
440 /* Request file change from file buffer. Returns true is next
441 track is available and changed. If return value is false,
442 codec should exit immediately with PLUGIN_OK status. */
443 static bool request_next_track(void)
445 /* We are only decoding a single track */
446 return false;
450 /* Free the buffer area of the current codec after its loaded */
451 static void discard_codec(void)
453 /* ??? */
457 static void set_offset(size_t value)
459 /* ??? */
460 (void)value;
464 /* Configure different codec buffer parameters. */
465 static void configure(int setting, intptr_t value)
467 if (use_dsp)
468 rb->dsp_configure(ci.dsp, setting, value);
469 switch(setting)
471 case DSP_SWITCH_FREQUENCY:
472 case DSP_SET_FREQUENCY:
473 DEBUGF("samplerate=%d\n",(int)value);
474 wavinfo.samplerate = (int)value;
475 break;
477 case DSP_SET_SAMPLE_DEPTH:
478 DEBUGF("sampledepth = %d\n",(int)value);
479 wavinfo.sampledepth=(int)value;
480 break;
482 case DSP_SET_STEREO_MODE:
483 DEBUGF("Stereo mode = %d\n",(int)value);
484 wavinfo.stereomode=(int)value;
485 break;
490 static void init_ci(void)
492 /* --- Our "fake" implementations of the codec API functions. --- */
494 ci.codec_get_buffer = codec_get_buffer;
496 if (wavinfo.fd >= 0 || checksum) {
497 ci.pcmbuf_insert = pcmbuf_insert_wav_checksum;
498 } else {
499 ci.pcmbuf_insert = pcmbuf_insert_null;
502 ci.set_elapsed = set_elapsed;
503 ci.read_filebuf = read_filebuf;
504 ci.request_buffer = request_buffer;
505 ci.advance_buffer = advance_buffer;
506 ci.advance_buffer_loc = advance_buffer_loc;
507 ci.seek_buffer = seek_buffer;
508 ci.seek_complete = seek_complete;
509 ci.request_next_track = request_next_track;
510 ci.discard_codec = discard_codec;
511 ci.set_offset = set_offset;
512 ci.configure = configure;
513 ci.dsp = (struct dsp_config *)rb->dsp_configure(NULL, DSP_MYDSP,
514 CODEC_IDX_AUDIO);
516 /* --- "Core" functions --- */
518 /* kernel/ system */
519 ci.sleep = rb->sleep;
520 ci.yield = rb->yield;
522 /* strings and memory */
523 ci.strcpy = rb->strcpy;
524 ci.strlen = rb->strlen;
525 ci.strcmp = rb->strcmp;
526 ci.strcat = rb->strcat;
527 ci.memset = rb->memset;
528 ci.memcpy = rb->memcpy;
529 ci.memmove = rb->memmove;
530 ci.memcmp = rb->memcmp;
531 ci.memchr = rb->memchr;
532 ci.strcasestr = rb->strcasestr;
533 #if defined(DEBUG) || defined(SIMULATOR)
534 ci.debugf = rb->debugf;
535 #endif
536 #ifdef ROCKBOX_HAS_LOGF
537 ci.logf = rb->logf;
538 #endif
540 ci.qsort = rb->qsort;
541 ci.global_settings = rb->global_settings;
543 #ifdef RB_PROFILE
544 ci.profile_thread = rb->profile_thread;
545 ci.profstop = rb->profstop;
546 ci.profile_func_enter = rb->profile_func_enter;
547 ci.profile_func_exit = rb->profile_func_exit;
548 #endif
550 #if NUM_CORES > 1
551 ci.cpucache_invalidate = rb->cpucache_invalidate;
552 ci.cpucache_flush = rb->cpucache_flush;
553 #endif
555 #if NUM_CORES > 1
556 ci.create_thread = rb->create_thread;
557 ci.thread_thaw = rb->thread_thaw;
558 ci.thread_wait = rb->thread_wait;
559 ci.semaphore_init = rb->semaphore_init;
560 ci.semaphore_wait = rb->semaphore_wait;
561 ci.semaphore_release = rb->semaphore_release;
562 #endif
564 #ifdef CPU_ARM
565 ci.__div0 = rb->__div0;
566 #endif
569 static void codec_thread(void)
571 const char* codecname;
572 int res;
574 codecname = rb->get_codec_filename(track.id3.codectype);
576 /* Load the codec and start decoding. */
577 res = rb->codec_load_file(codecname,&ci);
579 /* Signal to the main thread that we are done */
580 endtick = *rb->current_tick;
581 codec_playing = false;
584 static enum plugin_status test_track(const char* filename)
586 size_t n;
587 int fd;
588 enum plugin_status res = PLUGIN_ERROR;
589 long starttick;
590 long ticks;
591 unsigned long speed;
592 unsigned long duration;
593 const char* ch;
595 /* Display filename (excluding any path)*/
596 ch = rb->strrchr(filename, '/');
597 if (ch==NULL)
598 ch = filename;
599 else
600 ch++;
602 rb->snprintf(str,sizeof(str),"%s",ch);
603 log_text(str,true);
605 log_text("Loading...",false);
607 fd = rb->open(filename,O_RDONLY);
608 if (fd < 0)
610 log_text("Cannot open file",true);
611 goto exit;
614 track.filesize = rb->filesize(fd);
616 /* Clear the id3 struct */
617 rb->memset(&track.id3, 0, sizeof(struct mp3entry));
619 if (!rb->get_metadata(&(track.id3), fd, filename))
621 log_text("Cannot read metadata",true);
622 goto exit;
625 if (track.filesize > audiosize)
627 log_text("File too large",true);
628 goto exit;
631 n = rb->read(fd, audiobuf, track.filesize);
633 if (n != track.filesize)
635 log_text("Read failed.",true);
636 goto exit;
639 /* Initialise the function pointers in the codec API */
640 init_ci();
642 /* Prepare the codec struct for playing the whole file */
643 ci.filesize = track.filesize;
644 ci.id3 = &track.id3;
645 ci.taginfo_ready = &taginfo_ready;
646 ci.curpos = 0;
647 ci.stop_codec = false;
648 ci.new_track = 0;
649 ci.seek_time = 0;
651 if (use_dsp)
652 rb->dsp_configure(ci.dsp, DSP_RESET, 0);
654 if (checksum)
655 crc32 = 0xffffffff;
657 starttick = *rb->current_tick;
659 codec_playing = true;
661 rb->codec_thread_do_callback(codec_thread, NULL);
663 /* Wait for codec thread to die */
664 while (codec_playing)
666 rb->sleep(HZ);
667 rb->snprintf(str,sizeof(str),"%d of %d",elapsed,(int)track.id3.length);
668 log_text(str,false);
670 ticks = endtick - starttick;
672 /* Be sure it is done */
673 rb->codec_thread_do_callback(NULL, NULL);
675 log_text(str,true);
677 if (checksum)
679 rb->snprintf(str, sizeof(str), "CRC32 - %08x", (unsigned)crc32);
680 log_text(str,true);
682 else if (wavinfo.fd < 0)
684 /* Display benchmark information */
685 rb->snprintf(str,sizeof(str),"Decode time - %d.%02ds",(int)ticks/100,(int)ticks%100);
686 log_text(str,true);
688 duration = track.id3.length / 10;
689 rb->snprintf(str,sizeof(str),"File duration - %d.%02ds",(int)duration/100,(int)duration%100);
690 log_text(str,true);
692 if (ticks > 0)
693 speed = duration * 10000 / ticks;
694 else
695 speed = 0;
697 rb->snprintf(str,sizeof(str),"%d.%02d%% realtime",(int)speed/100,(int)speed%100);
698 log_text(str,true);
700 #if (CONFIG_PLATFORM & PLATFORM_NATIVE)
701 /* show effective clockrate in MHz needed for realtime decoding */
702 if (speed > 0)
704 speed = CPUFREQ_MAX / speed;
705 rb->snprintf(str,sizeof(str),"%d.%02dMHz needed for realtime",
706 (int)speed/100,(int)speed%100);
707 log_text(str,true);
709 #endif
712 res = PLUGIN_OK;
714 exit:
715 rb->backlight_on();
717 if (fd >= 0)
719 rb->close(fd);
722 return res;
725 /* plugin entry point */
726 enum plugin_status plugin_start(const void* parameter)
728 int result, selection = 0;
729 enum plugin_status res = PLUGIN_OK;
730 int scandir;
731 struct dirent *entry;
732 DIR* dir;
733 char* ch;
734 char dirpath[MAX_PATH];
735 char filename[MAX_PATH];
737 if (parameter == NULL)
739 rb->splash(HZ*2, "No File");
740 return PLUGIN_ERROR;
743 codec_mallocbuf = rb->plugin_get_audio_buffer(&audiosize);
744 audiobuf = SKIPBYTES(codec_mallocbuf, CODEC_SIZE);
745 audiosize -= CODEC_SIZE;
747 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
748 rb->cpu_boost(true);
749 #endif
750 rb->lcd_clear_display();
751 rb->lcd_update();
753 enum
755 SPEED_TEST = 0,
756 SPEED_TEST_DIR,
757 WRITE_WAV,
758 SPEED_TEST_WITH_DSP,
759 SPEED_TEST_DIR_WITH_DSP,
760 WRITE_WAV_WITH_DSP,
761 CHECKSUM,
762 CHECKSUM_DIR,
763 QUIT,
766 MENUITEM_STRINGLIST(
767 menu, "test_codec", NULL,
768 "Speed test",
769 "Speed test folder",
770 "Write WAV",
771 "Speed test with DSP",
772 "Speed test folder with DSP",
773 "Write WAV with DSP",
774 "Checksum",
775 "Checksum folder",
776 "Quit",
779 show_menu:
780 rb->lcd_clear_display();
782 result = rb->do_menu(&menu, &selection, NULL, false);
784 if (result == QUIT)
786 res = PLUGIN_OK;
787 goto exit;
790 scandir = 0;
792 if ((checksum = (result == CHECKSUM || result == CHECKSUM_DIR)))
793 result -= 6;
795 if ((use_dsp = ((result >= SPEED_TEST_WITH_DSP)
796 && (result <= WRITE_WAV_WITH_DSP)))) {
797 result -= 3;
799 if (result == SPEED_TEST) {
800 wavinfo.fd = -1;
801 log_init(false);
802 } else if (result == SPEED_TEST_DIR) {
803 wavinfo.fd = -1;
804 scandir = 1;
806 /* Only create a log file when we are testing a folder */
807 if (!log_init(true)) {
808 rb->splash(HZ*2, "Cannot create logfile");
809 res = PLUGIN_ERROR;
810 goto exit;
812 } else if (result == WRITE_WAV) {
813 log_init(false);
814 init_wav("/test.wav");
815 if (wavinfo.fd < 0) {
816 rb->splash(HZ*2, "Cannot create /test.wav");
817 res = PLUGIN_ERROR;
818 goto exit;
820 } else if (result == MENU_ATTACHED_USB) {
821 res = PLUGIN_USB_CONNECTED;
822 goto exit;
823 } else if (result < 0) {
824 res = PLUGIN_OK;
825 goto exit;
828 if (scandir) {
829 /* Test all files in the same directory as the file selected by the
830 user */
832 rb->strlcpy(dirpath,parameter,sizeof(dirpath));
833 ch = rb->strrchr(dirpath,'/');
834 ch[1]=0;
836 DEBUGF("Scanning directory \"%s\"\n",dirpath);
837 dir = rb->opendir(dirpath);
838 if (dir) {
839 entry = rb->readdir(dir);
840 while (entry) {
841 if (!(entry->attribute & ATTR_DIRECTORY)) {
842 rb->snprintf(filename,sizeof(filename),"%s%s",dirpath,entry->d_name);
843 test_track(filename);
844 log_text("", true);
847 /* Read next entry */
848 entry = rb->readdir(dir);
851 rb->closedir(dir);
853 } else {
854 /* Just test the file */
855 res = test_track(parameter);
857 /* Close WAV file (if there was one) */
858 if (wavinfo.fd >= 0) {
859 close_wav();
860 log_text("Wrote /test.wav",true);
862 while (rb->button_get(true) != TESTCODEC_EXITBUTTON);
864 rb->button_clear_queue();
865 goto show_menu;
867 exit:
868 log_close();
870 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
871 rb->cpu_boost(false);
872 #endif
874 return res;