From ec9b202a921996945ebd42a7edba1326754d7737 Mon Sep 17 00:00:00 2001 From: Jens Arnold Date: Sat, 10 Sep 2005 12:28:16 +0000 Subject: [PATCH] Reworked handling of MPEG audio frame & file info: * Fixed frame size calculation for layer 1 (all versions) and layer 2 (V2/V2.5). * Exact frame time (expressed as a fraction) for way more precise playtime calculation at 44100/22050/11025Hz sample frequency. * Exact playtime<->position conversion for CBR also used for resume position. * More compact code, long policy. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7505 a1c6a512-1295-4272-9138-f99709370657 --- apps/bookmark.c | 2 +- apps/playback.c | 10 +- apps/screens.c | 2 +- apps/settings.h | 4 +- apps/wps-display.c | 4 +- firmware/export/id3.h | 36 ++++--- firmware/export/mp3data.h | 26 ++--- firmware/id3.c | 8 +- firmware/mp3data.c | 193 +++++++++++++++++--------------------- firmware/mpeg.c | 12 +-- 10 files changed, 138 insertions(+), 159 deletions(-) diff --git a/apps/bookmark.c b/apps/bookmark.c index c157d01662..593bfe98cb 100644 --- a/apps/bookmark.c +++ b/apps/bookmark.c @@ -389,7 +389,7 @@ static char* create_bookmark() /* create the bookmark */ snprintf(global_bookmark, sizeof(global_bookmark), - "%d;%d;%d;%d;%d;%d;%d;%s;%s", + "%d;%ld;%d;%d;%ld;%d;%d;%s;%s", resume_index, id3->offset, playlist_get_seed(NULL), diff --git a/apps/playback.c b/apps/playback.c index 6fd63d5a0f..1677e3138b 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -2008,7 +2008,7 @@ static void mp3_set_elapsed(struct mp3entry* id3) /* find wich percent we're at */ for (i=0; i<100; i++ ) { - if ( id3->offset < (int)(id3->toc[i] * (id3->filesize / 256)) ) + if ( id3->offset < id3->toc[i] * (id3->filesize / 256) ) { break; } @@ -2048,8 +2048,8 @@ static void mp3_set_elapsed(struct mp3entry* id3) } } else - /* constant bitrate == simple frame calculation */ - id3->elapsed = id3->offset / id3->bpf * id3->tpf; + /* constant bitrate, use exact calculation */ + id3->elapsed = id3->offset / (id3->bitrate / 8); } /* Copied from mpeg.c. Should be moved somewhere else. */ @@ -2092,8 +2092,8 @@ int mp3_get_file_pos(void) (id3->elapsed / 1000); } } - else if (id3->bpf && id3->tpf) - pos = (id3->elapsed/id3->tpf)*id3->bpf; + else if (id3->bitrate) + pos = id3->elapsed * (id3->bitrate / 8); else { return -1; diff --git a/apps/screens.c b/apps/screens.c index 10e1cdd188..bba5b279e2 100644 --- a/apps/screens.c +++ b/apps/screens.c @@ -1398,7 +1398,7 @@ bool browse_id3(void) id3->vbr ? str(LANG_ID3_VBR) : (const unsigned char*) ""); line = draw_id3_item(line, top, LANG_ID3_BITRATE, buf); - snprintf(buf, sizeof(buf), "%d Hz", id3->frequency); + snprintf(buf, sizeof(buf), "%ld Hz", id3->frequency); line = draw_id3_item(line, top, LANG_ID3_FRECUENCY, buf); #if CONFIG_CODEC == SWCODEC diff --git a/apps/settings.h b/apps/settings.h index 1c1e664b90..327b79a7ee 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -215,13 +215,13 @@ struct user_settings #ifdef HAVE_SPDIF_POWER bool spdif_enable; /* S/PDIF power on/off */ #endif - + /* resume settings */ bool resume; /* resume option: 0=off, 1=on */ int resume_index; /* index in playlist (-1 for no active resume) */ int resume_first_index; /* index of first track in playlist */ - int resume_offset; /* byte offset in mp3 file */ + unsigned long resume_offset; /* byte offset in mp3 file */ int resume_seed; /* shuffle seed (-1=no resume shuffle 0=sorted >0=shuffled) */ diff --git a/apps/wps-display.c b/apps/wps-display.c index 1794e53549..3c3cc98ed4 100644 --- a/apps/wps-display.c +++ b/apps/wps-display.c @@ -598,7 +598,7 @@ static char* get_tag(struct mp3entry* cid3, return buf; case 'f': /* File Frequency */ - snprintf(buf, buf_size, "%d", id3->frequency); + snprintf(buf, buf_size, "%ld", id3->frequency); return buf; case 'p': /* File Path */ @@ -626,7 +626,7 @@ static char* get_tag(struct mp3entry* cid3, } case 's': /* File Size (in kilobytes) */ - snprintf(buf, buf_size, "%d", id3->filesize / 1024); + snprintf(buf, buf_size, "%ld", id3->filesize / 1024); return buf; case 'c': /* File Codec */ diff --git a/firmware/export/id3.h b/firmware/export/id3.h index 6c6507159a..47a084d67a 100644 --- a/firmware/export/id3.h +++ b/firmware/export/id3.h @@ -61,39 +61,37 @@ struct mp3entry { unsigned char genre; unsigned int codectype; unsigned int bitrate; - unsigned int frequency; - unsigned int id3v2len; - unsigned int id3v1len; - unsigned int first_frame_offset; /* Byte offset to first real MP3 frame. - Used for skipping leading garbage to - avoid gaps between tracks. */ - unsigned int vbr_header_pos; - unsigned int filesize; /* in bytes */ - unsigned int length; /* song length */ - unsigned int elapsed; /* ms played */ + unsigned long frequency; + unsigned long id3v2len; + unsigned long id3v1len; + unsigned long first_frame_offset; /* Byte offset to first real MP3 frame. + Used for skipping leading garbage to + avoid gaps between tracks. */ + unsigned long vbr_header_pos; + unsigned long filesize; /* without headers; in bytes */ + unsigned long length; /* song length in ms */ + unsigned long elapsed; /* ms played */ - int lead_trim; /* Number of samples to skip at the beginning */ - int tail_trim; /* Number of samples to remove from the end */ + int lead_trim; /* Number of samples to skip at the beginning */ + int tail_trim; /* Number of samples to remove from the end */ /* Added for Vorbis */ - unsigned long samples; /* number of samples in track */ + unsigned long samples; /* number of samples in track */ /* MP3 stream specific info */ - long bpf; /* bytes per frame */ - long tpf; /* time per frame */ - long frame_count; /* number of frames in the file (if VBR) */ + unsigned long frame_count; /* number of frames in the file (if VBR) */ /* Xing VBR fields */ bool vbr; - bool has_toc; /* True if there is a VBR header in the file */ - unsigned char toc[100];/* table of contents */ + 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 */ char id3v2buf[300]; char id3v1buf[3][32]; /* resume related */ - int offset; /* bytes played */ + unsigned long offset; /* bytes played */ int index; /* playlist index */ /* FileEntry fields */ diff --git a/firmware/export/mp3data.h b/firmware/export/mp3data.h index db1a93b8d6..3961664815 100644 --- a/firmware/export/mp3data.h +++ b/firmware/export/mp3data.h @@ -20,9 +20,9 @@ #ifndef _MP3DATA_H_ #define _MP3DATA_H_ -#define MPEG_VERSION2_5 0 -#define MPEG_VERSION1 1 -#define MPEG_VERSION2 2 +#define MPEG_VERSION1 0 +#define MPEG_VERSION2 1 +#define MPEG_VERSION2_5 2 struct mp3info { /* Standard MP3 frame header fields */ @@ -30,23 +30,25 @@ struct mp3info { int layer; bool protection; int bitrate; - int frequency; + long 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 */ + int frame_size; /* Frame size in bytes */ + int frame_samples; /* Samples per frame */ + int ft_num; /* Numerator of frametime in milliseconds */ + int ft_den; /* Denominator of frametime 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_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]; - long frame_count; /* Number of frames in the file (if VBR) */ - long byte_count; /* File size in bytes */ - long file_time; /* Length of the whole file in milliseconds */ - int vbr_header_pos; + unsigned long frame_count; /* Number of frames in the file (if VBR) */ + unsigned long byte_count; /* File size in bytes */ + unsigned long file_time; /* Length of the whole file in milliseconds */ + unsigned long vbr_header_pos; int enc_delay; /* Encoder delay, fetched from LAME header */ int enc_padding; /* Padded samples added to last frame. LAME header */ }; diff --git a/firmware/id3.c b/firmware/id3.c index f1aa821fba..dfbcb9699d 100644 --- a/firmware/id3.c +++ b/firmware/id3.c @@ -863,10 +863,10 @@ static int getid3v2len(int fd) */ static int getsonglength(int fd, struct mp3entry *entry) { - unsigned int filetime = 0; + unsigned long filetime = 0; struct mp3info info; - int bytecount; - + long bytecount; + /* Start searching after ID3v2 header */ if(-1 == lseek(fd, entry->id3v2len, SEEK_SET)) return 0; @@ -912,8 +912,6 @@ static int getsonglength(int fd, struct mp3entry *entry) * always multiples of 8, and it avoids overflows. */ } - entry->tpf = info.frame_time; - entry->bpf = info.frame_size; entry->frame_count = info.frame_count; entry->vbr = info.is_vbr; diff --git a/firmware/mp3data.c b/firmware/mp3data.c index 6f4c560578..c2d4cd2c37 100644 --- a/firmware/mp3data.c +++ b/firmware/mp3data.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "debug.h" #include "logf.h" #include "mp3data.h" @@ -39,7 +40,7 @@ #define DEBUG_VERBOSE -#define BYTES2INT(b1,b2,b3,b4) (((long)(b1 & 0xFF) << (3*8)) | \ +#define BYTES2INT(b1,b2,b3,b4) (((long)(b1 & 0xFF) << (3*8)) | \ ((long)(b2 & 0xFF) << (2*8)) | \ ((long)(b3 & 0xFF) << (1*8)) | \ ((long)(b4 & 0xFF) << (0*8))) @@ -58,37 +59,34 @@ #define ORIGINAL_MASK (1L << 2) #define EMPHASIS_MASK 3L -/* 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} - } +/* MPEG Version table, sorted by version index */ +static const signed char version_table[4] = { + MPEG_VERSION2_5, -1, MPEG_VERSION2, MPEG_VERSION1 }; -/* Table of samples per frame for MP3 files. - * Indexed by layer. Multiplied with 1000. - */ -const long bs[3] = {384000, 1152000, 1152000}; +/* Bitrate table for mpeg audio, indexed by row index and birate index */ +static const short bitrates[5][16] = { + {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, /* V1 L1 */ + {0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,0}, /* V1 L2 */ + {0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,0}, /* V1 L3 */ + {0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256,0}, /* V2 L1 */ + {0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0} /* V2 L2+L3 */ +}; -/* Table of sample frequency for MP3 files. - * Indexed by version and layer. - */ - -const int freqtab[][4] = +/* Bitrate pointer table, indexed by version and layer */ +static const short *bitrate_table[3][3] = { - {11025, 12000, 8000, 0}, /* MPEG version 2.5 */ - {44100, 48000, 32000, 0}, /* MPEG Version 1 */ - {22050, 24000, 16000, 0}, /* MPEG version 2 */ + {bitrates[0], bitrates[1], bitrates[2]}, + {bitrates[3], bitrates[4], bitrates[4]}, + {bitrates[3], bitrates[4], bitrates[4]} +}; + +/* Sampling frequency table, indexed by version and frequency index */ +static const long freq_table[3][3] = +{ + {44100, 48000, 32000}, /* MPEG Version 1 */ + {22050, 24000, 16000}, /* MPEG version 2 */ + {11025, 12000, 8000}, /* MPEG version 2.5 */ }; /* check if 'head' is a valid mp3 frame header */ @@ -117,97 +115,77 @@ static bool is_mp3frameheader(unsigned long head) static bool mp3headerinfo(struct mp3info *info, unsigned long header) { - int bittable = 0; - int bitindex; - int freqindex; - + int bitindex, freqindex; + /* MPEG Audio Version */ - switch((header & VERSION_MASK) >> 19) { - 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: + info->version = version_table[(header & VERSION_MASK) >> 19]; + if (info->version < 0) return false; - case 2: - /* MPEG version 2 (ISO/IEC 13818-3) */ - info->version = MPEG_VERSION2; - bittable = MPEG_VERSION2 - 1; - break; - - case 3: - /* MPEG version 1 (ISO/IEC 11172-3) */ - info->version = MPEG_VERSION1; - bittable = MPEG_VERSION1 - 1; - break; - } - - switch((header & LAYER_MASK) >> 17) { - case 0: + /* Layer */ + info->layer = 3 - ((header & LAYER_MASK) >> 17); + if (info->layer == 3) return false; - case 1: - info->layer = 2; - break; - case 2: - info->layer = 1; - break; - case 3: - info->layer = 0; - break; - } - info->protection = (header & PROTECTION_MASK)?true:false; + info->protection = (header & PROTECTION_MASK) ? true : false; /* Bitrate */ - bitindex = (header & 0xf000) >> 12; - info->bitrate = bitrate_table[bittable][info->layer][bitindex]; + bitindex = (header & BITRATE_MASK) >> 12; + info->bitrate = bitrate_table[info->version][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; + /* Sampling frequency */ + freqindex = (header & SAMPLERATE_MASK) >> 10; + if (freqindex == 3) + return false; + info->frequency = freq_table[info->version][freqindex]; + + info->padding = (header & PADDING_MASK) ? 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; + if (info->layer == 0) { + info->frame_samples = 384; + info->frame_size = (12000 * info->bitrate / info->frequency + + info->padding) * 4; + } + else { + if ((info->version > MPEG_VERSION1) && (info->layer == 2)) + info->frame_samples = 576; + else + info->frame_samples = 1152; + info->frame_size = (1000/8) * info->frame_samples * info->bitrate + / info->frequency + info->padding; } - info->frame_size += info->padding; + /* Frametime fraction calculation. + This fraction is reduced as far as possible. */ + if (freqindex != 0) { /* 48/32/24/16/12/8 kHz */ + /* integer number of milliseconds, denominator == 1 */ + info->ft_num = 1000 * info->frame_samples / info->frequency; + info->ft_den = 1; + } + else { /* 44.1/22.05/11.025 kHz */ + if (info->layer == 0) { + info->ft_num = 147000 * 384 / info->frequency; + info->ft_den = 147; + } + else { + info->ft_num = 49000 * info->frame_samples / info->frequency; + info->ft_den = 49; + } + } - /* 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; + info->channel_mode = (header & CHANNELMODE_MASK) >> 6; + info->mode_extension = (header & MODE_EXT_MASK) >> 4; + info->emphasis = header & EMPHASIS_MASK; #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", + DEBUGF( "Header: %08x, Ver %d, lay %d, bitr %d, freq %ld, " + "chmode %d, mode_ext %d, emph %d, bytes: %d time: %d/%d\n", header, info->version, info->layer+1, info->bitrate, info->frequency, info->channel_mode, info->mode_extension, - info->emphasis, info->frame_size, info->frame_time); + info->emphasis, info->frame_size, info->ft_num, info->ft_den); #endif return true; } @@ -252,7 +230,7 @@ static unsigned long __find_next_frame(int fd, long *offset, long max_offset, if(*offset) DEBUGF("Warning: skipping %d bytes of garbage\n", *offset); #endif - + return header; } @@ -380,9 +358,6 @@ int get_mp3file_info(int fd, struct mp3info *info) memset(info, 0, sizeof(struct mp3info)); /* These two are needed for proper LAME gapless MP3 playback */ - /* TODO: These can be found in a LAME Info header as well, but currently - they are only looked for in a Xing header. Xing and Info headers have - the exact same format, but Info headers are used for CBR files. */ info->enc_delay = -1; info->enc_padding = -1; if(!mp3headerinfo(info, header)) @@ -435,7 +410,10 @@ int get_mp3file_info(int fd, struct mp3info *info) { info->frame_count = BYTES2INT(vbrheader[i], vbrheader[i+1], vbrheader[i+2], vbrheader[i+3]); - info->file_time = info->frame_count * info->frame_time; + if (info->frame_count <= ULONG_MAX / info->ft_num) + info->file_time = info->frame_count * info->ft_num / info->ft_den; + else + info->file_time = info->frame_count / info->ft_den * info->ft_num; i += 4; } @@ -510,7 +488,10 @@ int get_mp3file_info(int fd, struct mp3info *info) 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; + if (info->frame_count <= ULONG_MAX / info->ft_num) + info->file_time = info->frame_count * info->ft_num / info->ft_den; + else + info->file_time = info->frame_count / info->ft_den * info->ft_num; info->bitrate = info->byte_count * 8 / info->file_time; /* We don't parse the TOC, since we don't yet know how to (FIXME) */ diff --git a/firmware/mpeg.c b/firmware/mpeg.c index ff74cf96a8..8d2d13b6ee 100644 --- a/firmware/mpeg.c +++ b/firmware/mpeg.c @@ -348,7 +348,7 @@ static void set_elapsed(struct mp3entry* id3) /* find wich percent we're at */ for (i=0; i<100; i++ ) { - if ( id3->offset < (int)(id3->toc[i] * (id3->filesize / 256)) ) + if ( id3->offset < id3->toc[i] * (id3->filesize / 256) ) { break; } @@ -388,8 +388,8 @@ static void set_elapsed(struct mp3entry* id3) } } else - /* constant bitrate == simple frame calculation */ - id3->elapsed = id3->offset / id3->bpf * id3->tpf; + /* constant bitrate, use exact calculation */ + id3->elapsed = id3->offset / (id3->bitrate / 8); } int audio_get_file_pos(void) @@ -405,7 +405,7 @@ int audio_get_file_pos(void) unsigned int percent, remainder; int curtoc, nexttoc, plen; - percent = (id3->elapsed*100)/id3->length; + percent = (id3->elapsed*100)/id3->length; if (percent > 99) percent = 99; @@ -431,8 +431,8 @@ int audio_get_file_pos(void) (id3->elapsed / 1000); } } - else if (id3->bpf && id3->tpf) - pos = (id3->elapsed/id3->tpf)*id3->bpf; + else if (id3->bitrate) + pos = id3->elapsed * (id3->bitrate / 8); else { return -1;