diff options
-rw-r--r-- | firmware/export/id3.h | 9 | ||||
-rw-r--r-- | firmware/export/mp3data.h | 66 | ||||
-rw-r--r-- | firmware/export/mpeg.h | 1 | ||||
-rw-r--r-- | firmware/id3.c | 371 | ||||
-rw-r--r-- | firmware/mp3data.c | 664 | ||||
-rw-r--r-- | firmware/mpeg.c | 320 | ||||
-rw-r--r-- | uisimulator/win32/Makefile | 5 | ||||
-rw-r--r-- | uisimulator/x11/Makefile | 5 |
8 files changed, 1016 insertions, 425 deletions
diff --git a/firmware/export/id3.h b/firmware/export/id3.h index 55ce002c2e..30be4bf883 100644 --- a/firmware/export/id3.h +++ b/firmware/export/id3.h @@ -39,15 +39,18 @@ struct mp3entry { unsigned int first_frame_offset; /* Byte offset to first real MP3 frame. Used for skipping leading garbage to avoid gaps between tracks. */ + unsigned int xing_header_pos; unsigned int filesize; /* in bytes */ unsigned int length; /* song length */ unsigned int elapsed; /* ms played */ + + /* MP3 stream specific info */ long bpf; /* bytes per frame */ long tpf; /* time per frame */ /* Xing VBR fields */ bool vbr; - unsigned char vbrflags; + bool has_toc; /* True if there is a VBR header in the file */ unsigned char toc[100];/* table of contents */ /* these following two fields are used for local buffering */ @@ -59,10 +62,6 @@ struct mp3entry { int index; /* playlist index */ }; -#define VBR_FRAMES_FLAG 0x01 -#define VBR_BYTES_FLAG 0x02 -#define VBR_TOC_FLAG 0x04 - enum { ID3_VER_1_0 = 1, ID3_VER_1_1, diff --git a/firmware/export/mp3data.h b/firmware/export/mp3data.h new file mode 100644 index 0000000000..a1018ebaa2 --- /dev/null +++ b/firmware/export/mp3data.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Linus Nielsen Feltzing + * + * 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. + * + ****************************************************************************/ + +#ifndef _MP3DATA_H_ +#define _MP3DATA_H_ + +#define MPEG_VERSION2_5 0 +#define MPEG_VERSION1 1 +#define MPEG_VERSION2 2 + +struct mp3info { + /* Standard MP3 frame header fields */ + int version; + int layer; + bool protection; + int bitrate; + int frequency; + int padding; + int channel_mode; + int mode_extension; + int emphasis; + int frame_size; /* Frame size in bytes */ + int frame_time; /* Frame duration in milliseconds */ + + bool is_vbr; /* True if the file is VBR */ + bool has_toc; /* True if there is a VBR header in the file */ + bool is_xing_vbr; /* True if the VBR header is of Xing type */ + bool is_vbri_vbr; /* True if the VBR header is of VBRI type */ + unsigned char toc[100]; + int frame_count; /* Number of frames in the file (if VBR) */ + int byte_count; /* File size in bytes */ + int file_time; /* Length of the whole file in milliseconds */ + int xing_header_pos; +}; + +/* Xing header information */ +#define VBR_FRAMES_FLAG 0x01 +#define VBR_BYTES_FLAG 0x02 +#define VBR_TOC_FLAG 0x04 + + +unsigned long find_next_frame(int fd, int *offset, int max_offset, unsigned long last_header); +int get_mp3file_info(int fd, struct mp3info *info); +int count_mp3_frames(int fd, int startpos, int filesize, + void (*progressfunc)(int)); +int create_xing_header(int fd, int startpos, int filesize, + unsigned char *buf, int num_frames, + void (*progressfunc)(int)); + +#endif diff --git a/firmware/export/mpeg.h b/firmware/export/mpeg.h index 6b44363f8f..9ce03daa7d 100644 --- a/firmware/export/mpeg.h +++ b/firmware/export/mpeg.h @@ -90,6 +90,7 @@ unsigned long mpeg_num_recorded_bytes(void); #endif void mpeg_get_debugdata(struct mpeg_debug *dbgdata); void mpeg_set_buffer_margin(int seconds); +int mpeg_create_xing_header(char *filename, void (*progressfunc)(int)); #define SOUND_VOLUME 0 #define SOUND_BASS 1 diff --git a/firmware/id3.c b/firmware/id3.c index 8e8a60eb71..6aeafc4749 100644 --- a/firmware/id3.c +++ b/firmware/id3.c @@ -22,9 +22,6 @@ * by David Härdeman. It has since been extended and enhanced pretty much by * all sorts of friendly Rockbox people. * - * A nice reference for MPEG header info: - * http://rockbox.haxx.se/docs/mpeghdr.html - * */ #include <stdio.h> @@ -37,6 +34,7 @@ #include "atoi.h" #include "id3.h" +#include "mp3data.h" #define UNSYNC(b0,b1,b2,b3) (((b0 & 0x7F) << (3*7)) | \ ((b1 & 0x7F) << (2*7)) | \ @@ -48,38 +46,6 @@ ((b2 & 0xFF) << (1*8)) | \ ((b3 & 0xFF) << (0*8))) -/* Table of bitrates for MP3 files, all values in kilo. - * Indexed by version, layer and value of bit 15-12 in header. - */ -const int bitrate_table[2][3][16] = -{ - { - {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, - {0,32,48,56, 64,80, 96, 112,128,160,192,224,256,320,384,0}, - {0,32,40,48, 56,64, 80, 96, 112,128,160,192,224,256,320,0} - }, - { - {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0}, - {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0}, - {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0} - } -}; - -/* Table of samples per frame for MP3 files. - * Indexed by layer. Multiplied with 1000. - */ -const int bs[4] = {0, 384000, 1152000, 1152000}; - -/* Table of sample frequency for MP3 files. - * Indexed by version and layer. - */ -const int freqtab[][4] = -{ - {11025, 12000, 8000, 0}, /* MPEG version 2.5 */ - {44100, 48000, 32000, 0}, /* MPEG Version 1 */ - {22050, 24000, 16000, 0}, /* MPEG version 2 */ -}; - /* Checks to see if the passed in string is a 16-bit wide Unicode v2 string. If it is, we attempt to convert it to a 8-bit ASCII string (for valid 8-bit ASCII characters). If it's not unicode, we leave @@ -168,6 +134,7 @@ static bool setid3v1title(int fd, struct mp3entry *entry) if (strncmp(buffer, "TAG", 3)) return false; + entry->id3v1len = 128; entry->id3version = ID3_VER_1_0; for (i=0; i < (int)sizeof offsets; i++) { @@ -239,6 +206,8 @@ static void setid3v2title(int fd, struct mp3entry *entry) char *tracknum = NULL; int bytesread = 0; int buffersize = sizeof(entry->id3v2buf); + int flags; + int skip; /* Bail out if the tag is shorter than 10 bytes */ if(entry->id3v2len < 10) @@ -275,17 +244,34 @@ static void setid3v2title(int fd, struct mp3entry *entry) } entry->id3version = version; + /* Skip the extended header if it is present */ + if(version >= ID3_VER_2_4) { + if(header[5] & 0x40) { + if(4 != read(fd, header, 4)) + return; + + framelen = UNSYNC(header[0], header[1], + header[2], header[3]); + + lseek(fd, framelen - 4, SEEK_CUR); + } + } + /* * We must have at least minframesize bytes left for the * remaining frames to be interesting */ while(size > minframesize) { + flags = 0; + /* Read frame header and check length */ if(version >= ID3_VER_2_3) { if(10 != read(fd, header, 10)) return; /* Adjust for the 10 bytes we read */ size -= 10; + + flags = BYTES2INT(0, 0, header[8], header[9]); if (version >= ID3_VER_2_4) { framelen = UNSYNC(header[4], header[5], @@ -311,6 +297,33 @@ static void setid3v2title(int fd, struct mp3entry *entry) if(framelen == 0) return; + if(flags) + { + skip = 0; + + if(flags & 0x0040) /* Grouping identity */ + skip++; + + if(flags & 0x000e) /* Compression, encryption or + unsynchronization */ + { + /* Skip it using the total size in case + it was truncated */ + size -= totframelen; + lseek(fd, totframelen, SEEK_CUR); + continue; + } + + if(flags & 0x0001) /* Data length indicator */ + skip += 4; + + if(skip) + { + lseek(fd, skip, SEEK_CUR); + framelen -= skip; + } + } + /* If the frame is larger than the remaining buffer space we try to read as much as would fit in the buffer */ if(framelen >= buffersize - bufferpos) @@ -404,6 +417,7 @@ static int getid3v2len(int fd) else offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10; + DEBUGF("ID3V2 Length: 0x%x\n", offset); return offset; } @@ -419,29 +433,6 @@ static int getfilesize(int fd) return size; } -/* check if 'head' is a valid mp3 frame header */ -static bool mp3frameheader(unsigned long head) -{ - if ((head & 0xffe00000) != 0xffe00000) /* bad sync? */ - return false; - if (!((head >> 17) & 3)) /* no layer? */ - return false; - if (((head >> 12) & 0xf) == 0xf) /* bad bitrate? */ - return false; - if (!((head >> 12) & 0xf)) /* no bitrate? */ - return false; - if (((head >> 10) & 0x3) == 0x3) /* bad sample rate? */ - return false; - if (((head >> 19) & 1) == 1 && - ((head >> 17) & 3) == 3 && - ((head >> 16) & 1) == 1) - return false; - if ((head & 0xffff0000) == 0xfffe0000) - return false; - - return true; -} - /* * Calculates the length (in milliseconds) of an MP3 file. * @@ -456,263 +447,48 @@ static bool mp3frameheader(unsigned long head) static int getsonglength(int fd, struct mp3entry *entry) { unsigned int filetime = 0; - unsigned long header=0; - unsigned char tmp; - unsigned char frame[156]; - unsigned char* xing; - - enum { - MPEG_VERSION2_5, - MPEG_VERSION1, - MPEG_VERSION2 - } version; - int layer; - int bitindex; - int bitrate; - int freqindex; - int frequency; - int chmode; + struct mp3info info; int bytecount; - int bytelimit; - int bittable; /* which bitrate table to use */ - bool header_found = false; - - long bpf; - long tpf; - + /* Start searching after ID3v2 header */ if(-1 == lseek(fd, entry->id3v2len, SEEK_SET)) return 0; - - /* Fill up header with first 24 bits */ - for(version = 0; version < 3; version++) { - header <<= 8; - if(!read(fd, &tmp, 1)) - return 0; - header |= tmp; - } - - /* Loop trough file until we find a frame header */ - bytecount = entry->id3v2len - 1; - bytelimit = entry->id3v2len + 0x20000; - restart: - do { - header <<= 8; - if(!read(fd, &tmp, 1)) - return 0; - header |= tmp; - /* Quit if we haven't found a valid header within 128K */ - bytecount++; - if(bytecount > bytelimit) - return 0; - } while(!mp3frameheader(header)); - - /* - * Some files are filled with garbage in the beginning, - * if the bitrate index of the header is binary 1111 - * that is a good indicator - */ - if((header & 0xF000) == 0xF000) - goto restart; - - /* MPEG Audio Version */ - switch((header & 0x180000) >> 19) { - case 0: - /* MPEG version 2.5 is not an official standard */ - version = MPEG_VERSION2_5; - bittable = MPEG_VERSION2; /* use the V2 bit rate table */ - break; + bytecount = get_mp3file_info(fd, &info); - case 2: - /* MPEG version 2 (ISO/IEC 13818-3) */ - version = MPEG_VERSION2; - bittable = MPEG_VERSION2; - break; + DEBUGF("Space between ID3V2 tag and first audio frame: 0x%x bytes\n", + bytecount); - case 3: - /* MPEG version 1 (ISO/IEC 11172-3) */ - version = MPEG_VERSION1; - bittable = MPEG_VERSION1; - break; - default: - goto restart; - } - - /* Layer */ - switch((header & 0x060000) >> 17) { - case 1: - layer = 3; - break; - case 2: - layer = 2; - break; - case 3: - layer = 1; - break; - default: - goto restart; - } - - /* Bitrate */ - bitindex = (header & 0xF000) >> 12; - bitrate = bitrate_table[bittable-1][layer-1][bitindex]; - if(bitrate == 0) - goto restart; - - /* Sampling frequency */ - freqindex = (header & 0x0C00) >> 10; - frequency = freqtab[version][freqindex]; - if(frequency == 0) - goto restart; - -#ifdef DEBUG_VERBOSE - DEBUGF( "Version %i, lay %i, biti %i, bitr %i, freqi %i, freq %i, chmode %d\n", - version, layer, bitindex, bitrate, freqindex, frequency, chmode); -#endif - entry->version = version; - entry->layer = layer; - entry->frequency = frequency; - - /* Calculate bytes per frame, calculation depends on layer */ - switch(layer) { - case 1: - bpf = bitrate_table[bittable - 1][layer - 1][bitindex]; - bpf *= 48000; - bpf /= freqtab[version][freqindex] << (bittable - 1); - break; - case 2: - case 3: - bpf = bitrate_table[bittable - 1][layer - 1][bitindex]; - bpf *= 144000; - bpf /= freqtab[version][freqindex] << (bittable - 1); - break; - default: - bpf = 1; - } - - /* Calculate time per frame */ - tpf = bs[layer] / (freqtab[version][freqindex] << (bittable - 1)); - - entry->bpf = bpf; - entry->tpf = tpf; - - /* OK, we have found a frame. Let's see if it has a Xing header */ - if(read(fd, frame, sizeof frame) < 0) + if(bytecount < 0) return -1; + + bytecount += entry->id3v2len; - /* Channel mode (stereo/mono) */ - chmode = (header & 0xc0) >> 6; - - /* calculate position of Xing VBR header */ - if ( version == 1 ) { - if ( chmode == 3 ) /* mono */ - xing = frame + 17; - else - xing = frame + 32; - } - else { - if ( chmode == 3 ) /* mono */ - xing = frame + 9; - else - xing = frame + 17; - } - - if (xing[0] == 'X' && - xing[1] == 'i' && - xing[2] == 'n' && - xing[3] == 'g') - { - int i = 8; /* Where to start parsing info */ - - /* Yes, it is a VBR file */ - entry->vbr = true; - entry->vbrflags = xing[7]; - - if (entry->vbrflags & VBR_FRAMES_FLAG) /* Is the frame count there? */ - { - int framecount = (xing[i] << 24) | (xing[i+1] << 16) | - (xing[i+2] << 8) | xing[i+3]; - - filetime = framecount * tpf; - i += 4; - } - - if (entry->vbrflags & VBR_BYTES_FLAG) /* is byte count there? */ - { - int bytecount = (xing[i] << 24) | (xing[i+1] << 16) | - (xing[i+2] << 8) | xing[i+3]; - - bitrate = bytecount * 8 / filetime; - i += 4; - } - - if (entry->vbrflags & VBR_TOC_FLAG) /* is table-of-contents there? */ - { - memcpy( entry->toc, xing+i, 100 ); - } - - /* Make sure we skip this frame in playback */ - bytecount += bpf; - - header_found = true; - } - - if (xing[0] == 'V' && - xing[1] == 'B' && - xing[2] == 'R' && - xing[3] == 'I') - { - int framecount; - int bytecount; - - /* Yes, it is a FhG VBR file */ - entry->vbr = true; - entry->vbrflags = 0; - - bytecount = (xing[10] << 24) | (xing[11] << 16) | - (xing[12] << 8) | xing[13]; - - framecount = (xing[14] << 24) | (xing[15] << 16) | - (xing[16] << 8) | xing[17]; - - filetime = framecount * tpf; - bitrate = bytecount * 8 / filetime; - - /* We don't parse the TOC, since we don't yet know how to (FIXME) */ - - /* Make sure we skip this frame in playback */ - bytecount += bpf; - - header_found = true; - } - - /* Is it a LAME Info frame? */ - if (xing[0] == 'I' && - xing[1] == 'n' && - xing[2] == 'f' && - xing[3] == 'o') - { - /* Make sure we skip this frame in playback */ - bytecount += bpf; - - header_found = true; - } - - - entry->bitrate = bitrate; + entry->bitrate = info.bitrate; /* If the file time hasn't been established, this may be a fixed rate MP3, so just use the default formula */ + + filetime = info.file_time; + if(filetime == 0) { /* * Now song length is * ((filesize)/(bytes per frame))*(time per frame) */ - filetime = entry->filesize/bpf*tpf; + filetime = entry->filesize/info.frame_size*info.frame_time; } + entry->tpf = info.frame_time; + entry->bpf = info.frame_size; + + entry->vbr = info.is_vbr; + entry->has_toc = info.has_toc; + memcpy(entry->toc, info.toc, sizeof(info.toc)); + + entry->xing_header_pos = info.xing_header_pos; + /* Update the seek point for the first playable frame */ entry->first_frame_offset = bytecount; DEBUGF("First frame is at %x\n", entry->first_frame_offset); @@ -720,7 +496,6 @@ static int getsonglength(int fd, struct mp3entry *entry) return filetime; } - /* * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc) * about an MP3 file and updates it's entry accordingly. @@ -750,6 +525,10 @@ bool mp3info(struct mp3entry *entry, char *filename) setid3v2title(fd, entry); entry->length = getsonglength(fd, entry); + /* Subtract the meta information from the file size to get + the true size of the MP3 stream */ + entry->filesize -= entry->first_frame_offset; + /* only seek to end of file if no id3v2 tags were found */ if (!entry->id3v2len) { if(!entry->title) diff --git a/firmware/mp3data.c b/firmware/mp3data.c new file mode 100644 index 0000000000..8d925041ce --- /dev/null +++ b/firmware/mp3data.c @@ -0,0 +1,664 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Daniel Stenberg + * + * 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. + * + ****************************************************************************/ + +/* + * Parts of this code has been stolen from the Ample project and was written + * by David Härdeman. It has since been extended and enhanced pretty much by + * all sorts of friendly Rockbox people. + * + * A nice reference for MPEG header info: + * http://rockbox.haxx.se/docs/mpeghdr.html + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include "debug.h" +#include "mp3data.h" +#include "file.h" + +#define DEBUG_VERBOSE + +#define BYTES2INT(b1,b2,b3,b4) (((b1 & 0xFF) << (3*8)) | \ + ((b2 & 0xFF) << (2*8)) | \ + ((b3 & 0xFF) << (1*8)) | \ + ((b4 & 0xFF) << (0*8))) + +#define SYNC_MASK (0x7ff << 21) +#define VERSION_MASK (3 << 19) +#define LAYER_MASK (3 << 17) +#define PROTECTION_MASK (1 << 16) +#define BITRATE_MASK (0xf << 12) +#define SAMPLERATE_MASK (3 << 10) +#define PADDING_MASK (1 << 9) +#define PRIVATE_MASK (1 << 8) +#define CHANNELMODE_MASK (3 << 6) +#define MODE_EXT_MASK (3 << 4) +#define COPYRIGHT_MASK (1 << 3) +#define ORIGINAL_MASK (1 << 2) +#define EMPHASIS_MASK 3 + +/* Table of bitrates for MP3 files, all values in kilo. + * Indexed by version, layer and value of bit 15-12 in header. + */ +const int bitrate_table[2][3][16] = +{ + { + {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, + {0,32,48,56, 64,80, 96, 112,128,160,192,224,256,320,384,0}, + {0,32,40,48, 56,64, 80, 96, 112,128,160,192,224,256,320,0} + }, + { + {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0}, + {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0}, + {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0} + } +}; + +/* Table of samples per frame for MP3 files. + * Indexed by layer. Multiplied with 1000. + */ +const int bs[3] = {384000, 1152000, 1152000}; + +/* Table of sample frequency for MP3 files. + * Indexed by version and layer. + */ + +const int freqtab[][4] = +{ + {11025, 12000, 8000, 0}, /* MPEG version 2.5 */ + {44100, 48000, 32000, 0}, /* MPEG Version 1 */ + {22050, 24000, 16000, 0}, /* MPEG version 2 */ +}; + +/* check if 'head' is a valid mp3 frame header */ +static bool is_mp3frameheader(unsigned long head) +{ + if ((head & SYNC_MASK) != (unsigned long)SYNC_MASK) /* bad sync? */ + return false; + if ((head & VERSION_MASK) == (1 << 19)) /* bad version? */ + return false; + if (!(head & LAYER_MASK)) /* no layer? */ + return false; + if ((head & BITRATE_MASK) == BITRATE_MASK) /* bad bitrate? */ + return false; + if (!(head & BITRATE_MASK)) /* no bitrate? */ + return false; + if ((head & SAMPLERATE_MASK) == SAMPLERATE_MASK) /* bad sample rate? */ + return false; + if (((head >> 19) & 1) == 1 && + ((head >> 17) & 3) == 3 && + ((head >> 16) & 1) == 1) + return false; + if ((head & 0xffff0000) == 0xfffe0000) + return false; + + return true; +} + +static bool mp3headerinfo(struct mp3info *info, unsigned long header) +{ + int bittable = 0; + int bitindex; + int freqindex; + + /* MPEG Audio Version */ + switch(header & VERSION_MASK) { + case 0: + /* MPEG version 2.5 is not an official standard */ + info->version = MPEG_VERSION2_5; + bittable = MPEG_VERSION2 - 1; /* use the V2 bit rate table */ + break; + + case (1 << 19): + return false; + + case (2 << 19): + /* MPEG version 2 (ISO/IEC 13818-3) */ + info->version = MPEG_VERSION2; + bittable = MPEG_VERSION2 - 1; + break; + + case (3 << 19): + /* MPEG version 1 (ISO/IEC 11172-3) */ + info->version = MPEG_VERSION1; + bittable = MPEG_VERSION1 - 1; + break; + } + + switch(header & LAYER_MASK) { + case 0: + return false; + case (1 << 17): + info->layer = 2; + break; + case (2 << 17): + info->layer = 1; + break; + case (3 << 17): + info->layer = 0; + break; + } + + info->protection = (header & PROTECTION_MASK)?true:false; + + /* Bitrate */ + bitindex = (header & 0xf000) >> 12; + info->bitrate = bitrate_table[bittable][info->layer][bitindex]; + if(info->bitrate == 0) + return false; + + /* Sampling frequency */ + freqindex = (header & 0x0C00) >> 10; + info->frequency = freqtab[info->version][freqindex]; + if(info->frequency == 0) + return false; + + info->padding = (header & 0x0200)?1:0; + + /* Calculate number of bytes, calculation depends on layer */ + switch(info->layer) { + case 0: + info->frame_size = info->bitrate * 48000; + info->frame_size /= + freqtab[info->version][freqindex] << bittable; + break; + case 1: + case 2: + info->frame_size = info->bitrate * 144000; + info->frame_size /= + freqtab[info->version][freqindex] << bittable; + break; + default: + info->frame_size = 1; + } + + info->frame_size += info->padding; + + /* Calculate time per frame */ + info->frame_time = bs[info->layer] / + (freqtab[info->version][freqindex] << bittable); + + info->channel_mode = (header & 0xc0) >> 6; + info->mode_extension = (header & 0x30) >> 4; + info->emphasis = header & 3; + +#ifdef DEBUG_VERBOSE + DEBUGF( "Header: %08x, Ver %d, lay %d, bitr %d, freq %d, " + "chmode %d, mode_ext %d, emph %d, bytes: %d time: %d\n", + header, info->version, info->layer, info->bitrate, info->frequency, + info->channel_mode, info->mode_extension, + info->emphasis, info->frame_size, info->frame_time); +#endif + return true; +} + +unsigned long find_next_frame(int fd, int *offset, int max_offset, unsigned long last_header) +{ + unsigned long header=0; + unsigned char tmp; + int i; + + int pos = 0; + + /* We remember the last header we found, to use as a template to see if + the header we find has the same frequency, layer etc */ + last_header &= 0xffff0c00; + + /* Fill up header with first 24 bits */ + for(i = 0; i < 3; i++) { + header <<= 8; + if(!read(fd, &tmp, 1)) + return 0; + header |= tmp; + pos++; + } + + do { + header <<= 8; + if(!read(fd, &tmp, 1)) + return 0; + header |= tmp; + pos++; + if(max_offset > 0 && pos > max_offset) + return 0; + } while(!is_mp3frameheader(header) || + (last_header?((header & 0xffff0c00) != last_header):false)); + + *offset = pos - 4; + +#ifdef DEBUG + if(*offset) + DEBUGF("Warning: skipping %d bytes of garbage\n", *offset); +#endif + + return header; +} + +#ifdef SIMULATOR +unsigned char mp3buf[0x100000]; +unsigned char mp3end[1]; +#else +extern unsigned char mp3buf[]; +extern unsigned char mp3end[]; +#endif +static int fnf_read_index; +static int fnf_buf_len; + +static int fd; + +static int buf_getbyte(unsigned char *c) +{ + if(fnf_read_index < fnf_buf_len) + { + *c = mp3buf[fnf_read_index++]; + return 1; + } + else + { + fnf_buf_len = read(fd, mp3buf, mp3end - mp3buf); + if(fnf_buf_len < 0) + return -1; + + fnf_read_index = 0; + + if(fnf_buf_len > 0) + { + *c = mp3buf[fnf_read_index++]; + return 1; + } + else + return 0; + } + return 0; +} + +static int buf_seek(int len) +{ + fnf_read_index += len; + if(fnf_read_index > fnf_buf_len) + { + len = fnf_read_index - fnf_buf_len; + + fnf_buf_len = read(fd, mp3buf, mp3end - mp3buf); + if(fnf_buf_len < 0) + return -1; + + fnf_read_index = 0; + fnf_read_index += len; + } + + if(fnf_read_index > fnf_buf_len) + { + return -1; + } + else + return 0; +} + +static void buf_init(void) +{ + fnf_buf_len = 0; + fnf_read_index = 0; +} + +unsigned long buf_find_next_frame(int *offset, int max_offset, + unsigned long last_header) +{ + unsigned long header=0; + unsigned char tmp; + int i; + + int pos = 0; + + /* We remember the last header we found, to use as a template to see if + the header we find has the same frequency, layer etc */ + last_header &= 0xffff0c00; + + /* Fill up header with first 24 bits */ + for(i = 0; i < 3; i++) { + header <<= 8; + if(!buf_getbyte(&tmp)) + return 0; + header |= tmp; + pos++; + } + + do { + header <<= 8; + if(!buf_getbyte(&tmp)) + return 0; + header |= tmp; + pos++; + if(max_offset > 0 && pos > max_offset) + return 0; + } while(!is_mp3frameheader(header) || + (last_header?((header & 0xffff0c00) != last_header):false)); + + *offset = pos - 4; + +#ifdef DEBUG + if(*offset) + DEBUGF("Warning: skipping %d bytes of garbage\n", *offset); +#endif + + return header; +} + +int get_mp3file_info(int fd, struct mp3info *info) +{ + unsigned char frame[1024]; + unsigned char *vbrheader; + unsigned long header; + int bytecount; + int num_offsets; + int frames_per_entry; + int i; + int offset; + int j; + int tmp; + + header = find_next_frame(fd, &bytecount, 0x20000, 0); + /* Quit if we haven't found a valid header within 128K */ + if(header == 0) + return -1; + + memset(info, 0, sizeof(struct mp3info)); + if(!mp3headerinfo(info, header)) + return -2; + + /* OK, we have found a frame. Let's see if it has a Xing header */ + if(read(fd, frame, info->frame_size-4) < 0) + return -3; + + /* calculate position of VBR header */ + if ( info->version == MPEG_VERSION1 ) { + if (info->channel_mode == 3) /* mono */ + vbrheader = frame + 17; + else + vbrheader = frame + 32; + } + else { + if (info->channel_mode == 3) /* mono */ + vbrheader = frame + 9; + else + vbrheader = frame + 17; + } + + if (vbrheader[0] == 'X' && + vbrheader[1] == 'i' && + vbrheader[2] == 'n' && + vbrheader[3] == 'g') + { + int i = 8; /* Where to start parsing info */ + + DEBUGF("Xing header\n"); + + /* Remember where in the file the Xing header is */ + info->xing_header_pos = lseek(fd, 0, SEEK_CUR) - info->frame_size; + + /* We want to skip the Xing frame when playing the stream */ + bytecount += info->frame_size; + + /* Now get the next frame to find out the real info about + the mp3 stream */ + header = find_next_frame(fd, &tmp, 0x20000, 0); + if(header == 0) + return -4; + + if(!mp3headerinfo(info, header)) + return -5; + + /* Yes, it is a VBR file */ + info->is_vbr = true; + info->is_xing_vbr = true; + + if(vbrheader[7] & VBR_FRAMES_FLAG) /* Is the frame count there? */ + { + info->frame_count = BYTES2INT(vbrheader[i], vbrheader[i+1], + vbrheader[i+2], vbrheader[i+3]); + info->file_time = info->frame_count * info->frame_time; + i += 4; + } + + if(vbrheader[7] & VBR_BYTES_FLAG) /* Is byte count there? */ + { + info->byte_count = BYTES2INT(vbrheader[i], vbrheader[i+1], + vbrheader[i+2], vbrheader[i+3]); + info->bitrate = info->byte_count * 8 / info->file_time; + i += 4; + } + + if(vbrheader[7] & VBR_TOC_FLAG) /* Is table-of-contents there? */ + { + memcpy( info->toc, vbrheader+i, 100 ); + } + } + + if (vbrheader[0] == 'V' && + vbrheader[1] == 'B' && + vbrheader[2] == 'R' && + vbrheader[3] == 'I') + { + DEBUGF("VBRI header\n"); + + /* We want to skip the VBRI frame when playing the stream */ + bytecount += info->frame_size; + + /* Now get the next frame to find out the real info about + the mp3 stream */ + header = find_next_frame(fd, &bytecount, 0x20000, 0); + if(header == 0) + return -6; + + if(!mp3headerinfo(info, header)) + return -7; + + DEBUGF("%04x: %04x %04x ", 0, header >> 16, header & 0xffff); + for(i = 4;i < (int)sizeof(frame)-4;i+=2) { + if(i % 16 == 0) { + DEBUGF("\n%04x: ", i-4); + } + DEBUGF("%04x ", (frame[i-4] << 8) | frame[i-4+1]); + } + + DEBUGF("\n"); + + /* Yes, it is a FhG VBR file */ + info->is_vbr = true; + info->is_vbri_vbr = true; + info->has_toc = false; /* We don't parse the TOC (yet) */ + + info->byte_count = BYTES2INT(vbrheader[10], vbrheader[11], + vbrheader[12], vbrheader[13]); + info->frame_count = BYTES2INT(vbrheader[14], vbrheader[15], + vbrheader[16], vbrheader[17]); + + info->file_time = info->frame_count * info->frame_time; + info->bitrate = info->byte_count * 8 / info->file_time; + + /* We don't parse the TOC, since we don't yet know how to (FIXME) */ + num_offsets = BYTES2INT(0, 0, vbrheader[18], vbrheader[19]); + frames_per_entry = BYTES2INT(0, 0, vbrheader[24], vbrheader[25]); + DEBUGF("Frame size (%dkpbs): %d bytes (0x%x)\n", + info->bitrate, info->frame_size, info->frame_size); + DEBUGF("Frame count: %x\n", info->frame_count); + DEBUGF("Byte count: %x\n", info->byte_count); + DEBUGF("Offsets: %d\n", num_offsets); + DEBUGF("Frames/entry: %d\n", frames_per_entry); + + offset = 0; + + for(i = 0;i < num_offsets;i++) + { + j = BYTES2INT(0, 0, vbrheader[26+i*2], vbrheader[27+i*2]); + offset += j; + DEBUGF("%03d: %x (%x)\n", i, offset - bytecount, j); + } + } + + /* Is it a LAME Info frame? */ + if (vbrheader[0] == 'I' && + vbrheader[1] == 'n' && + vbrheader[2] == 'f' && + vbrheader[3] == 'o') + { + /* Make sure we skip this frame in playback */ + bytecount += info->frame_size; + } + + return bytecount; +} + +/* This is an MP3 header, 128kbit/s, 44.1kHz, with silence */ +static const unsigned char xing_frame_header[] = { + 0xff, 0xfa, 0x90, 0x64, 0x86, 0x1f +}; + +static const char cooltext[] = "Rockbox rocks"; + +static void int2bytes(unsigned char *buf, int val) +{ + buf[0] = (val >> 24) & 0xff; + buf[1] = (val >> 16) & 0xff; + buf[2] = (val >> 8) & 0xff; + buf[3] = val & 0xff; +} + +int count_mp3_frames(int fd, int startpos, int filesize, + void (*progressfunc)(int)) +{ + unsigned long header = 0; + struct mp3info info; + int num_frames; + int bytes; + int cnt; + int progress_chunk = filesize / 50; /* Max is 50%, in 1% increments */ + int progress_cnt = 0; + + if(lseek(fd, startpos, SEEK_SET) < 0) + return -1; + + buf_init(); + + /* Find out the total number of frames */ + num_frames = 0; + cnt = 0; + + while((header = buf_find_next_frame(&bytes, -1, header))) { + mp3headerinfo(&info, header); + buf_seek(info.frame_size-4); + num_frames++; + if(progressfunc) + { + cnt += bytes + info.frame_size; + if(cnt > progress_chunk) + { + progress_cnt++; + progressfunc(progress_cnt); + cnt = 0; + } + } + } + DEBUGF("Total number of frames: %d\n", num_frames); + + return num_frames; +} + +int create_xing_header(int fd, int startpos, int filesize, + unsigned char *buf, int num_frames, + void (*progressfunc)(int)) +{ + unsigned long header = 0; + struct mp3info info; + int pos, last_pos; + int i, j; + int bytes; + int filepos; + int tocentry; + int x; + + DEBUGF("create_xing_header()\n"); + + /* Create the frame header */ + memset(buf, 0, 417); + memcpy(buf, xing_frame_header, 6); + + lseek(fd, startpos, SEEK_SET); + buf_init(); + + buf[36] = 'X'; + buf[36+1] = 'i'; + buf[36+2] = 'n'; + buf[36+3] = 'g'; + int2bytes(&buf[36+4], (VBR_FRAMES_FLAG | VBR_BYTES_FLAG | VBR_TOC_FLAG)); + int2bytes(&buf[36+8], num_frames); + int2bytes(&buf[36+12], filesize - startpos); + + /* Generate filepos table */ + last_pos = 0; + filepos = 0; + header = 0; + x = 0; + for(i = 0;i < 100;i++) { + /* Calculate the absolute frame number for this seek point */ + pos = i * num_frames / 100; + + /* Advance from the last seek point to this one */ + for(j = 0;j < pos - last_pos;j++) + { + DEBUGF("fpos: %x frame no: %x ", filepos, x++); + header = buf_find_next_frame(&bytes, -1, header); + mp3headerinfo(&info, header); + buf_seek(info.frame_size-4); + filepos += info.frame_size; + } + + if(progressfunc) + { + progressfunc(50 + i/2); + } + + tocentry = filepos * 256 / filesize; + + DEBUGF("Pos %d: %d relpos: %d filepos: %x tocentry: %x\n", + i, pos, pos-last_pos, filepos, tocentry); + + /* Fill in the TOC entry */ + buf[36+16+i] = tocentry; + + last_pos = pos; + } + + memcpy(buf+152, cooltext, sizeof(cooltext)); + +#ifdef DEBUG + for(i = 0;i < 417;i++) + { + if(i && !(i % 16)) + DEBUGF("\n"); + + DEBUGF("%02x ", buf[i]); + } +#endif + + return 0; +} diff --git a/firmware/mpeg.c b/firmware/mpeg.c index 9470e7d84b..92f11e1b84 100644 --- a/firmware/mpeg.c +++ b/firmware/mpeg.c @@ -26,6 +26,7 @@ #include "string.h" #include <kernel.h> #include "thread.h" +#include "mp3data.h" #ifndef SIMULATOR #include "i2c.h" #include "mas.h" @@ -302,7 +303,7 @@ static void remove_all_tags(void) static void set_elapsed(struct mp3entry* id3) { if ( id3->vbr ) { - if ( id3->vbrflags & VBR_TOC_FLAG ) { + if ( id3->has_toc ) { /* calculate elapsed time using TOC */ int i; unsigned int remainder, plen, relpos, nextpos; @@ -1482,7 +1483,7 @@ static void mpeg_thread(void) if (id3->vbr) { - if (id3->vbrflags & VBR_TOC_FLAG) + if (id3->has_toc) { /* Use the TOC to find the new position */ unsigned int percent, remainder; @@ -1528,10 +1529,10 @@ static void mpeg_thread(void) transition properly to the next song */ newpos = id3->filesize - id3->id3v1len - 1; } - else if (newpos < (int)id3->id3v2len) + else if (newpos < (int)id3->first_frame_offset) { - /* skip past id3v2 tag */ - newpos = id3->id3v2len; + /* skip past id3v2 tag and other leading garbage */ + newpos = id3->first_frame_offset; } if (mpeg_file >= 0) @@ -1720,7 +1721,7 @@ static void mpeg_thread(void) t2 = current_tick; DEBUGF("time: %d\n", t2 - t1); DEBUGF("R: %x\n", len); - + /* Now make sure that we don't feed the MAS with ID3V1 data */ if (len < amount_to_read) @@ -1734,19 +1735,19 @@ static void mpeg_thread(void) { if(tagptr >= mp3buflen) tagptr -= mp3buflen; - + if(mp3buf[tagptr] != tag[i]) taglen = 0; - + tagptr++; } - + if(taglen) { /* Skip id3v1 tag */ DEBUGF("Skipping ID3v1 tag\n"); len -= taglen; - + /* The very rare case when the entire tag wasn't read in this read() call must be taken care of */ @@ -1819,131 +1820,135 @@ static void mpeg_thread(void) } else { - /* This doesn't look neccessary... - yield(); - if(!queue_empty(&mpeg_queue)) - {*/ - queue_wait(&mpeg_queue, &ev); - switch(ev.id) - { - case MPEG_RECORD: - DEBUGF("Recording...\n"); - reset_mp3_buffer(); - start_recording(); - demand_irq_enable(true); - mpeg_file = creat(recording_filename, O_WRONLY); - - if(mpeg_file < 0) - panicf("recfile: %d", mpeg_file); - - close(mpeg_file); - mpeg_file = -1; - break; - - case MPEG_STOP: - DEBUGF("MPEG_STOP\n"); - demand_irq_enable(false); - stop_recording(); - - /* Save the remaining data in the buffer */ - stop_pending = true; - queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0); - break; + queue_wait(&mpeg_queue, &ev); + switch(ev.id) + { + case MPEG_RECORD: + DEBUGF("Recording...\n"); + reset_mp3_buffer(); - case MPEG_STOP_DONE: - DEBUGF("MPEG_STOP_DONE\n"); + /* Advance the write pointer 4096+417 bytes to make + room for an ID3 tag plus a VBR header */ + mp3buf_write = 4096+417; + memset(mp3buf, 0, 4096+417); - if(mpeg_file >= 0) - close(mpeg_file); - mpeg_file = -1; + start_recording(); + demand_irq_enable(true); + + mpeg_file = creat(recording_filename, O_WRONLY); + + if(mpeg_file < 0) + panicf("recfile: %d", mpeg_file); + + close(mpeg_file); + + mpeg_file = -1; + break; + + case MPEG_STOP: + DEBUGF("MPEG_STOP\n"); + demand_irq_enable(false); + stop_recording(); + + /* Save the remaining data in the buffer */ + stop_pending = true; + queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0); + break; + + case MPEG_STOP_DONE: + DEBUGF("MPEG_STOP_DONE\n"); + + if(mpeg_file >= 0) + close(mpeg_file); + mpeg_file = -1; + #ifdef DEBUG1 + { + int i; + for(i = 0;i < 512;i++) { - int i; - for(i = 0;i < 512;i++) - { - DEBUGF("%d - %d us (%d bytes)\n", - timing_info[i*2], - (timing_info[i*2+1] & 0xffff) * - 10000 / 13824, - timing_info[i*2+1] >> 16); - } + DEBUGF("%d - %d us (%d bytes)\n", + timing_info[i*2], + (timing_info[i*2+1] & 0xffff) * + 10000 / 13824, + timing_info[i*2+1] >> 16); } + } #endif - mpeg_stop_done = true; - break; - - case MPEG_SAVE_DATA: - amount_to_save = mp3buf_write - mp3buf_read; - - /* If the result is negative, the write index has - wrapped */ - if(amount_to_save < 0) - { - amount_to_save += mp3buflen; - } - - DEBUGF("r: %x w: %x\n", mp3buf_read, mp3buf_write); - DEBUGF("ats: %x\n", amount_to_save); - /* Save data only if the buffer is getting full, - or if we should stop recording */ - if(amount_to_save) + mpeg_stop_done = true; + break; + + case MPEG_SAVE_DATA: + amount_to_save = mp3buf_write - mp3buf_read; + + /* If the result is negative, the write index has + wrapped */ + if(amount_to_save < 0) + { + amount_to_save += mp3buflen; + } + + DEBUGF("r: %x w: %x\n", mp3buf_read, mp3buf_write); + DEBUGF("ats: %x\n", amount_to_save); + /* Save data only if the buffer is getting full, + or if we should stop recording */ + if(amount_to_save) + { + if(mp3buflen - amount_to_save < MPEG_LOW_WATER || + stop_pending) { - if(mp3buflen - amount_to_save < MPEG_LOW_WATER || - stop_pending) - { - int rc; - - /* Only save up to the end of the buffer */ - writelen = MIN(amount_to_save, - mp3buflen - mp3buf_read); - - DEBUGF("wrl: %x\n", writelen); - mpeg_file = open(recording_filename, - O_WRONLY| O_APPEND); - if(mpeg_file < 0) - panicf("rec open: %d", mpeg_file); - - rc = write(mpeg_file, mp3buf + mp3buf_read, - writelen); - - if(rc < 0) - panicf("rec wrt: %d", rc); - - rc = close(mpeg_file); - if(rc < 0) - panicf("rec cls: %d", rc); - - mpeg_file = -1; - DEBUGF("rc: %x\n", rc); - - mp3buf_read += amount_to_save; - if(mp3buf_read >= mp3buflen) - mp3buf_read = 0; - - queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0); - } - else - { - saving = false; - } + int rc; + + /* Only save up to the end of the buffer */ + writelen = MIN(amount_to_save, + mp3buflen - mp3buf_read); + + DEBUGF("wrl: %x\n", writelen); + mpeg_file = open(recording_filename, + O_WRONLY| O_APPEND); + if(mpeg_file < 0) + panicf("rec open: %d", mpeg_file); + + rc = write(mpeg_file, mp3buf + mp3buf_read, + writelen); + + if(rc < 0) + panicf("rec wrt: %d", rc); + + rc = close(mpeg_file); + if(rc < 0) + panicf("rec cls: %d", rc); + + mpeg_file = -1; + DEBUGF("rc: %x\n", rc); + + mp3buf_read += amount_to_save; + if(mp3buf_read >= mp3buflen) + mp3buf_read = 0; + + queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0); } else { - /* We have saved all data, - time to stop for real */ - if(stop_pending) - queue_post(&mpeg_queue, MPEG_STOP_DONE, 0); saving = false; } - break; - - case MPEG_INIT_PLAYBACK: - init_playback(); - init_playback_done = true; - break; - } - /*}*/ + } + else + { + /* We have saved all data, + time to stop for real */ + if(stop_pending) + queue_post(&mpeg_queue, MPEG_STOP_DONE, 0); + saving = false; + } + break; + + case MPEG_INIT_PLAYBACK: + init_playback(); + init_playback_done = true; + break; + } } #endif } @@ -2981,3 +2986,74 @@ void mpeg_init(int volume, int bass, int treble, int balance, int loudness, dbg_cnt2us(0); #endif } + +int d_1; +int d_2; + +int mpeg_create_xing_header(char *filename, void (*progressfunc)(int)) +{ + struct mp3entry entry; + char xingbuf[417]; + int fd; + int rc; + int flen; + int num_frames; + int fpos; + + if(progressfunc) + progressfunc(0); + + rc = mp3info(&entry, filename); + if(rc < 0) + return rc * 10 - 1; + + fd = open(filename, O_RDWR); + if(fd < 0) + return fd * 10 - 2; + + flen = lseek(fd, 0, SEEK_END); + + d_1 = entry.first_frame_offset; + d_2 = entry.filesize; + + if(progressfunc) + progressfunc(0); + + num_frames = count_mp3_frames(fd, entry.first_frame_offset, + flen, + progressfunc); + + create_xing_header(fd, entry.first_frame_offset, + flen, xingbuf, num_frames, progressfunc); + + /* Try to fit the Xing header first in the stream. Replace the existing + Xing header if there is one, else see if there is room between the + ID3 tag and the first MP3 frame. */ + if(entry.xing_header_pos) + { + /* Reuse existing Xing header */ + fpos = entry.xing_header_pos; + } + else + { + /* Any room between ID3 tag and first MP3 frame? */ + if(entry.first_frame_offset - entry.id3v2len > 417) + { + fpos = entry.first_frame_offset - 417; + } + else + { + close(fd); + return -3; + } + } + + lseek(fd, fpos, SEEK_SET); + write(fd, xingbuf, 417); + close(fd); + + if(progressfunc) + progressfunc(100); + + return 0; +} diff --git a/uisimulator/win32/Makefile b/uisimulator/win32/Makefile index d152ea7933..5d3ad06c76 100644 --- a/uisimulator/win32/Makefile +++ b/uisimulator/win32/Makefile @@ -89,7 +89,7 @@ ifeq ($(DISPLAY),-DHAVE_LCD_BITMAP) else LCDSRSC = lcd-playersim.c lcd-player.c lcd-player-charset.c font-player.c endif -FIRMSRCS = $(LCDSRSC) id3.c usb.c mpeg.c powermgmt.c power.c $(EXTRAFIRMSRC) +FIRMSRCS = $(LCDSRSC) id3.c mp3data.c usb.c mpeg.c powermgmt.c power.c $(EXTRAFIRMSRC) APPS = main.c tree.c menu.c credits.c main_menu.c icons.c language.c \ playlist.c wps.c wps-display.c settings.c status.c \ @@ -234,6 +234,9 @@ $(OBJDIR)/settings.o: $(APPDIR)/settings.c $(OBJDIR)/id3.o: $(FIRMWAREDIR)/id3.c $(CC) $(CFLAGS) -c $< -o $@ +$(OBJDIR)/mp3data.o: $(FIRMWAREDIR)/mp3data.c + $(CC) $(CFLAGS) -c $< -o $@ + $(OBJDIR)/font.o: $(FIRMWAREDIR)/font.c $(CC) $(CFLAGS) -c $< -o $@ diff --git a/uisimulator/x11/Makefile b/uisimulator/x11/Makefile index 0715ac7c70..414725811d 100644 --- a/uisimulator/x11/Makefile +++ b/uisimulator/x11/Makefile @@ -93,7 +93,7 @@ else LCDSRSC = lcd-playersim.c lcd-player.c font-player.c lcd-player-charset.c endif FIRMSRCS = $(LCDSRSC) id3.c debug.c usb.c mpeg.c power.c\ - powermgmt.c panic.c + powermgmt.c panic.c mp3data.c APPS = main.c tree.c menu.c credits.c main_menu.c language.c\ playlist.c wps.c wps-display.c settings.c status.c icons.c\ @@ -275,6 +275,9 @@ $(OBJDIR)/peakmeter.o: $(RECDIR)/peakmeter.c $(OBJDIR)/id3.o: $(FIRMWAREDIR)/id3.c $(CC) $(APPCFLAGS) -c $< -o $@ +$(OBJDIR)/mp3data.o: $(FIRMWAREDIR)/mp3data.c + $(CC) $(APPCFLAGS) -c $< -o $@ + $(OBJDIR)/debug.o: $(FIRMWAREDIR)/debug.c $(CC) $(CFLAGS) -c $< -o $@ |