From 02eaeafcb67c1caf73aa0e362901c7197a53c561 Mon Sep 17 00:00:00 2001 From: Dave Chapman Date: Wed, 16 Feb 2005 01:38:22 +0000 Subject: Initial check-in of mpa2wav - a test "viewer" plugin for the iRiver to convert an MPEG audio file (MP1/2/3) into a wav file (currently hard-coded to /libmadtest.wav git-svn-id: svn://svn.rockbox.org/rockbox/trunk@5966 a1c6a512-1295-4272-9138-f99709370657 --- apps/plugins/Makefile | 6 +- apps/plugins/SOURCES | 4 + apps/plugins/mpa2wav.c | 480 ++++++++++++++++++++++++++++++++++++++++++++ apps/plugins/viewers.config | 2 + 4 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 apps/plugins/mpa2wav.c diff --git a/apps/plugins/Makefile b/apps/plugins/Makefile index 5d735bfe56..12126a0d3d 100644 --- a/apps/plugins/Makefile +++ b/apps/plugins/Makefile @@ -16,6 +16,10 @@ ifdef APPEXTRA INCLUDES += -I$(APPSDIR)/$(APPEXTRA) endif +ifdef SOFTWARECODECS + CODECLIBS = -lmad +endif + LDS := plugin.lds LINKFILE := $(OBJDIR)/pluginlink.lds DEPFILE = $(OBJDIR)/dep-plugins @@ -32,7 +36,7 @@ all: $(OBJDIR)/libplugin.a $(ROCKS) $(DEPFILE) $(OBJDIR)/%.elf: $(OBJDIR)/%.o $(LINKFILE) $(OBJDIR)/libplugin.a @echo "LD $@" - @$(CC) $(GCCOPTS) -O -nostdlib -o $@ $< -L$(OBJDIR) -lplugin -lgcc -T$(LINKFILE) -Wl,-Map,$(OBJDIR)/$*.map + @$(CC) $(GCCOPTS) -O -nostdlib -o $@ $< -L$(OBJDIR) $(CODECLIBS) -lplugin -lgcc -T$(LINKFILE) -Wl,-Map,$(OBJDIR)/$*.map $(OBJDIR)/%.rock : $(OBJDIR)/%.elf @echo "OBJCOPY $<" diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index e3ef0fd194..39efee6154 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -65,3 +65,7 @@ nim.c /* not for Ondio, has no remote control pin */ alpine_cdc.c #endif + +#if SOFTWARECODECS == yes +mpa2wav.c +#endif diff --git a/apps/plugins/mpa2wav.c b/apps/plugins/mpa2wav.c new file mode 100644 index 0000000000..bff7608d98 --- /dev/null +++ b/apps/plugins/mpa2wav.c @@ -0,0 +1,480 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 Dave Chapman + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "plugin.h" + +#include + +typedef struct ao_sample_format { + int bits; /* bits per sample */ + int rate; /* samples per second (in a single channel) */ + int channels; /* number of audio channels */ + int byte_format; /* Byte ordering in sample, see constants below */ +} ao_sample_format; + +typedef struct { + int infile; + int outfile; + off_t curpos; + off_t filesize; + ao_sample_format samfmt; /* bits, rate, channels, byte_format */ + int framesize; + unsigned long total_samples; + unsigned long current_sample; +} file_info_struct; + + +struct mad_stream Stream; +struct mad_frame Frame; +struct mad_synth Synth; +mad_timer_t Timer; +struct dither d0, d1; + +file_info_struct file_info; + +#define MALLOC_BUFSIZE (512*1024) + +int mem_ptr; +int bufsize; +unsigned char* mp3buf; // The actual MP3 buffer from Rockbox +unsigned char* mallocbuf; // 512K from the start of MP3 buffer +unsigned char* filebuf; // The rest of the MP3 buffer + +/* here is a global api struct pointer. while not strictly necessary, + it's nice not to have to pass the api pointer in all function calls + in the plugin */ +static struct plugin_api* rb; + +void* malloc(size_t size) { + void* x; + char s[32]; + + x=&mallocbuf[mem_ptr]; + mem_ptr+=size+(size%4); // Keep memory 32-bit aligned (if it was already?) + + rb->snprintf(s,30,"Memory used: %d",mem_ptr); + rb->lcd_putsxy(0,80,s); + rb->lcd_update(); + return(x); +} + +void* calloc(size_t nmemb, size_t size) { + void* x; + x=malloc(nmemb*size); + rb->memset(x,0,nmemb*size); + return(x); +} + +void free(void* ptr) { + (void)ptr; +} + +void* realloc(void* ptr, size_t size) { + void* x; + (void)ptr; + x=malloc(size); + return(x); +} + +void *memcpy(void *dest, const void *src, size_t n) { + return(rb->memcpy(dest,src,n)); +} + +void *memset(void *s, int c, size_t n) { + return(rb->memset(s,c,n)); +} + +int memcmp(const void *s1, const void *s2, size_t n) { + return(rb->memcmp(s1,s2,n)); +} + +void* memmove(const void *s1, const void *s2, size_t n) { + char* dest=(char*)s1; + char* src=(char*)s2; + size_t i; + + for (i=0;i0) { *(dest++)=*(src++); n--; } + return(dest); +} + +void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *)) { + rb->qsort(base,nmemb,size,compar); +} + + +void abort(void) { + /* Let's hope this is never called by libmad */ +} + +/* The "dither" code to convert the 24-bit samples produced by libmad was + taken from the coolplayer project - coolplayer.sourceforge.net */ + +struct dither { + mad_fixed_t error[3]; + mad_fixed_t random; +}; + +# define SAMPLE_DEPTH 16 +# define scale(x, y) dither((x), (y)) + +/* + * NAME: prng() + * DESCRIPTION: 32-bit pseudo-random number generator + */ +static __inline +unsigned long prng(unsigned long state) +{ + return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; +} + +/* + * NAME: dither() + * DESCRIPTION: dither and scale sample + */ +static __inline +signed int dither(mad_fixed_t sample, struct dither *dither) +{ + unsigned int scalebits; + mad_fixed_t output, mask, random; + + enum { + MIN = -MAD_F_ONE, + MAX = MAD_F_ONE - 1 + }; + + /* noise shape */ + sample += dither->error[0] - dither->error[1] + dither->error[2]; + + dither->error[2] = dither->error[1]; + dither->error[1] = dither->error[0] / 2; + + /* bias */ + output = sample + (1L << (MAD_F_FRACBITS + 1 - SAMPLE_DEPTH - 1)); + + scalebits = MAD_F_FRACBITS + 1 - SAMPLE_DEPTH; + mask = (1L << scalebits) - 1; + + /* dither */ + random = prng(dither->random); + output += (random & mask) - (dither->random & mask); + + dither->random = random; + + /* clip */ + if (output > MAX) { + output = MAX; + + if (sample > MAX) + sample = MAX; + } + else if (output < MIN) { + output = MIN; + + if (sample < MIN) + sample = MIN; + } + + /* quantize */ + output &= ~mask; + + /* error feedback */ + dither->error[0] = sample - output; + + /* scale */ + return output >> scalebits; +} + +#define SHRT_MAX 32767 + +static unsigned char wav_header[44]={'R','I','F','F', // 0 - ChunkID + 0,0,0,0, // 4 - ChunkSize (filesize-8) + 'W','A','V','E', // 8 - Format + 'f','m','t',' ', // 12 - SubChunkID + 16,0,0,0, // 16 - SubChunk1ID // 16 for PCM + 1,0, // 20 - AudioFormat (1=16-bit) + 2,0, // 22 - NumChannels + 0,0,0,0, // 24 - SampleRate in Hz + 0,0,0,0, // 28 - Byte Rate (SampleRate*NumChannels*(BitsPerSample/8) + 4,0, // 32 - BlockAlign (== NumChannels * BitsPerSample/8) + 16,0, // 34 - BitsPerSample + 'd','a','t','a', // 36 - Subchunk2ID + 0,0,0,0 // 40 - Subchunk2Size + }; + +void close_wav(file_info_struct* file_info) { + int x; + int filesize=rb->filesize(file_info->outfile); + + /* We assume 16-bit, Stereo */ + + rb->lseek(file_info->outfile,0,SEEK_SET); + + // ChunkSize + x=filesize-8; + wav_header[4]=(x&0xff); + wav_header[5]=(x&0xff00)>>8; + wav_header[6]=(x&0xff0000)>>16; + wav_header[7]=(x&0xff000000)>>24; + + // Samplerate + wav_header[24]=file_info->samfmt.rate&0xff; + wav_header[25]=(file_info->samfmt.rate&0xff00)>>8; + wav_header[26]=(file_info->samfmt.rate&0xff0000)>>16; + wav_header[27]=(file_info->samfmt.rate&0xff000000)>>24; + + // ByteRate + x=file_info->samfmt.rate*4; + wav_header[28]=(x&0xff); + wav_header[29]=(x&0xff00)>>8; + wav_header[30]=(x&0xff0000)>>16; + wav_header[31]=(x&0xff000000)>>24; + + // Subchunk2Size + x=filesize-44; + wav_header[40]=(x&0xff); + wav_header[41]=(x&0xff00)>>8; + wav_header[42]=(x&0xff0000)>>16; + wav_header[43]=(x&0xff000000)>>24; + + rb->write(file_info->outfile,wav_header,sizeof(wav_header)); + rb->close(file_info->outfile); +} + +#define INPUT_BUFFER_SIZE (5*8192) +#define OUTPUT_BUFFER_SIZE 8192 /* Must be an integer multiple of 4. */ + +unsigned char InputBuffer[INPUT_BUFFER_SIZE+MAD_BUFFER_GUARD]; +unsigned char OutputBuffer[OUTPUT_BUFFER_SIZE]; +unsigned char *OutputPtr=OutputBuffer; +unsigned char *GuardPtr=NULL; +const unsigned char *OutputBufferEnd=OutputBuffer+OUTPUT_BUFFER_SIZE; + +/* this is the plugin entry point */ +enum plugin_status plugin_start(struct plugin_api* api, void* file) +{ + int i,n,bytesleft; + char s[32]; + unsigned long ticks_taken; + unsigned long start_tick; + unsigned long long speed; + unsigned long xspeed; + long file_ptr; + + int Status=0; + unsigned long FrameCount=0; + + TEST_PLUGIN_API(api); + + rb = api; + + mem_ptr=0; + mp3buf=rb->plugin_get_mp3_buffer(&bufsize); + mallocbuf=mp3buf; + filebuf=&mp3buf[MALLOC_BUFSIZE]; + + rb->snprintf(s,32,"mp3 bufsize: %d",bufsize); + rb->lcd_putsxy(0,100,s); + rb->lcd_update(); + + /* Create a decoder instance */ + + mad_stream_init(&Stream); + mad_frame_init(&Frame); + mad_synth_init(&Synth); + mad_timer_reset(&Timer); + + //if error: return PLUGIN_ERROR; + + file_info.infile=rb->open(file,O_RDONLY); + file_info.outfile=rb->creat("/libmadtest.wav",O_WRONLY); + rb->write(file_info.outfile,wav_header,sizeof(wav_header)); + file_info.curpos=0; + file_info.filesize=rb->filesize(file_info.infile); + + if (file_info.filesize > (bufsize-512*1024)) { + rb->close(file_info.infile); + rb->splash(HZ*2, true, "File too large"); + return PLUGIN_ERROR; + } + + rb->snprintf(s,32,"Loading file..."); + rb->lcd_putsxy(0,0,s); + rb->lcd_update(); + + bytesleft=file_info.filesize; + i=0; + while (bytesleft > 0) { + n=rb->read(file_info.infile,&filebuf[i],bytesleft); + if (n < 0) { + rb->close(file_info.infile); + rb->splash(HZ*2, true, "ERROR READING FILE"); + return PLUGIN_ERROR; + } + n+=i; bytesleft-=n; + } + rb->close(file_info.infile); + + mad_stream_init(&Stream); + mad_frame_init(&Frame); + mad_synth_init(&Synth); + mad_timer_reset(&Timer); + + file_ptr=0; + start_tick=*(rb->current_tick); + + rb->button_clear_queue(); + + /* This is the decoding loop. */ + while (file_ptr < file_info.filesize) { + if(Stream.buffer==NULL || Stream.error==MAD_ERROR_BUFLEN) { + size_t ReadSize, Remaining; + unsigned char *ReadStart; + + if(Stream.next_frame!=NULL) { + Remaining=Stream.bufend-Stream.next_frame; + memmove(InputBuffer,Stream.next_frame,Remaining); + ReadStart=InputBuffer+Remaining; + ReadSize=INPUT_BUFFER_SIZE-Remaining; + } else { + ReadSize=INPUT_BUFFER_SIZE; + ReadStart=InputBuffer; + Remaining=0; + } + + /* Fill-in the buffer. If an error occurs print a message + * and leave the decoding loop. If the end of stream is + * reached we also leave the loop but the return status is + * left untouched. + */ + + if ((file_info.filesize-file_ptr) < (int) ReadSize) { + ReadSize=file_info.filesize-file_ptr; + } + memcpy(ReadStart,&filebuf[file_ptr],ReadSize); + file_ptr+=ReadSize; + + if (file_ptr >= file_info.filesize) + { + GuardPtr=ReadStart+ReadSize; + memset(GuardPtr,0,MAD_BUFFER_GUARD); + ReadSize+=MAD_BUFFER_GUARD; + } + + /* Pipe the new buffer content to libmad's stream decoder + * facility. + */ + mad_stream_buffer(&Stream,InputBuffer,ReadSize+Remaining); + Stream.error=0; + } + + if(mad_frame_decode(&Frame,&Stream)) + { + if(MAD_RECOVERABLE(Stream.error)) + { + if(Stream.error!=MAD_ERROR_LOSTSYNC || Stream.this_frame!=GuardPtr) + { + rb->splash(HZ*1, true, "Recoverable...!"); + } + continue; + } + else + if(Stream.error==MAD_ERROR_BUFLEN) + continue; + else + { + rb->splash(HZ*1, true, "Recoverable...!"); + //fprintf(stderr,"%s: unrecoverable frame level error.\n",ProgName); + Status=1; + break; + } + } + + /* We assume all frames have same samplerate as the first */ + if(FrameCount==0) { + file_info.samfmt.rate=Frame.header.samplerate; + } + + FrameCount++; + /* ?? Do we need the timer module? */ + mad_timer_add(&Timer,Frame.header.duration); + +/* DAVE: This can be used to attenuate the audio */ +// if(DoFilter) +// ApplyFilter(&Frame); + + mad_synth_frame(&Synth,&Frame); + + /* Convert MAD's numbers to an array of 16-bit LE signed integers */ + for(i=0;i>8; + + /* Right channel. If the decoded stream is monophonic then + * the right output channel is the same as the left one. + */ + if(MAD_NCHANNELS(&Frame.header)==2) + Sample=scale(Synth.pcm.samples[1][i],&d1); + *(OutputPtr++)=Sample&0xff; + *(OutputPtr++)=Sample>>8; + + /* Flush the buffer if it is full. */ + if(OutputPtr==OutputBufferEnd) + { + rb->write(file_info.outfile,OutputBuffer,OUTPUT_BUFFER_SIZE); + OutputPtr=OutputBuffer; + } + } + + rb->snprintf(s,32,"Bytes Read: %d ",file_ptr); + rb->lcd_putsxy(0,0,s); + + rb->snprintf(s,32,"Samples Decoded: %d",file_info.current_sample); + rb->lcd_putsxy(0,20,s); + rb->snprintf(s,32,"Frames Decoded: %d",FrameCount); + rb->lcd_putsxy(0,40,s); + + file_info.current_sample+=Synth.pcm.length; + + ticks_taken=*(rb->current_tick)-start_tick; + + if (ticks_taken==0) { ticks_taken=1; } // Avoid fp exception. + + speed=(100*file_info.current_sample)/file_info.samfmt.rate; + xspeed=(speed*10000)/ticks_taken; + rb->snprintf(s,32,"Speed %ld.%02ld%% Secs: %d",(xspeed/100),(xspeed%100),ticks_taken/100); + rb->lcd_putsxy(0,60,s); + + rb->lcd_update(); + if (rb->button_get(false)!=BUTTON_NONE) { + close_wav(&file_info); + return PLUGIN_OK; + } + } + + close_wav(&file_info); + rb->splash(HZ*2, true, "FINISHED!"); + + return PLUGIN_OK; +} diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index a04c1640a5..bf2488fea4 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -6,3 +6,5 @@ rvf,video.rock,5D 7F 5D 7F 5D 7F mp3,vbrfix.rock,10 08 58 38 04 02 m3u,search.rock,00 00 00 00 00 00 txt,sort.rock, 00 00 00 00 00 00 +mp2,mpa2wav.rock, 00 00 00 00 00 00 +mp3,mpa2wav.rock, 00 00 00 00 00 00 -- cgit v1.2.3