From 51b45d56029eb8b928be64fc50332f3ba10e0228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Stenberg?= Date: Wed, 15 Oct 2008 06:38:51 +0000 Subject: Split id3.c/h into metadata.c/h and metadata/mp3.c. Updated all references. Moved mp3data.c/h from firmware to apps. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@18814 a1c6a512-1295-4272-9138-f99709370657 --- apps/SOURCES | 3 +- apps/codecs.h | 2 +- apps/codecs/lib/codeclib.c | 2 +- apps/cuesheet.h | 2 +- apps/gui/gwps.h | 2 +- apps/gui/statusbar.c | 2 +- apps/id3.c | 1353 -------------------------------------- apps/id3.h | 246 ------- apps/menus/recording_menu.c | 2 +- apps/metadata.c | 171 ++++- apps/metadata.h | 219 +++++- apps/metadata/a52.c | 2 +- apps/metadata/adx.c | 2 +- apps/metadata/aiff.c | 2 +- apps/metadata/ape.c | 2 +- apps/metadata/asap.c | 2 +- apps/metadata/asf.c | 2 +- apps/metadata/flac.c | 2 +- apps/metadata/metadata_common.c | 3 +- apps/metadata/metadata_common.h | 2 +- apps/metadata/metadata_parsers.h | 6 +- apps/metadata/mod.c | 2 +- apps/metadata/monkeys.c | 2 +- apps/metadata/mp3.c | 1183 +++++++++++++++++++++++++++++++++ apps/metadata/mp4.c | 2 +- apps/metadata/mpc.c | 2 +- apps/metadata/ogg.c | 2 +- apps/metadata/sid.c | 2 +- apps/metadata/spc.c | 2 +- apps/metadata/vorbis.c | 2 +- apps/metadata/wave.c | 2 +- apps/metadata/wavpack.c | 2 +- apps/mp3data.c | 782 ++++++++++++++++++++++ apps/mp3data.h | 83 +++ apps/mpeg.c | 2 +- apps/mpeg.h | 2 +- apps/onplay.c | 2 +- apps/playlist.h | 2 +- apps/plugin.h | 2 +- apps/recorder/albumart.c | 2 +- apps/recorder/albumart.h | 2 +- apps/recorder/icons.c | 2 +- apps/recorder/icons.h | 5 +- apps/recorder/pcm_record.c | 2 +- apps/replaygain.c | 2 +- apps/replaygain.h | 2 +- apps/screens.c | 2 +- apps/scrobbler.c | 2 +- apps/tagcache.c | 2 +- apps/tagcache.h | 2 +- apps/talk.c | 2 +- firmware/SOURCES | 1 - firmware/export/mp3data.h | 83 --- firmware/mp3data.c | 782 ---------------------- 54 files changed, 2487 insertions(+), 2513 deletions(-) delete mode 100644 apps/id3.c delete mode 100644 apps/id3.h create mode 100644 apps/metadata/mp3.c create mode 100644 apps/mp3data.c create mode 100644 apps/mp3data.h delete mode 100644 firmware/export/mp3data.h delete mode 100644 firmware/mp3data.c diff --git a/apps/SOURCES b/apps/SOURCES index 3fce356..d68ca1b 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -7,7 +7,6 @@ abrepeat.c bookmark.c debug_menu.c filetypes.c -id3.c language.c main.c menu.c @@ -31,6 +30,7 @@ menus/recording_menu.c menus/settings_menu.c menus/sound_menu.c misc.c +mp3data.c onplay.c playlist.c playlist_catalog.c @@ -130,6 +130,7 @@ eq_arm.S #endif #endif metadata.c +metadata/mp3.c #if CONFIG_CODEC == SWCODEC metadata/metadata_common.c metadata/aiff.c diff --git a/apps/codecs.h b/apps/codecs.h index 2cb642c..becb73c 100644 --- a/apps/codecs.h +++ b/apps/codecs.h @@ -36,7 +36,7 @@ #include "config.h" #include "kernel.h" #include "system.h" -#include "id3.h" +#include "metadata.h" #include "audio.h" #ifdef RB_PROFILE #include "profile.h" diff --git a/apps/codecs/lib/codeclib.c b/apps/codecs/lib/codeclib.c index 342e6b7..e537995 100644 --- a/apps/codecs/lib/codeclib.c +++ b/apps/codecs/lib/codeclib.c @@ -25,7 +25,7 @@ #include "codecs.h" #include "dsp.h" #include "codeclib.h" -#include "id3.h" +#include "metadata.h" long mem_ptr; long bufsize; diff --git a/apps/cuesheet.h b/apps/cuesheet.h index b6cf239..de51512 100644 --- a/apps/cuesheet.h +++ b/apps/cuesheet.h @@ -25,7 +25,7 @@ #include #include "screens.h" #include "file.h" -#include "id3.h" +#include "metadata.h" #define MAX_NAME 80 /* Max length of information strings */ #define MAX_TRACKS 99 /* Max number of tracks in a cuesheet */ diff --git a/apps/gui/gwps.h b/apps/gui/gwps.h index 579a340..6a4849c 100644 --- a/apps/gui/gwps.h +++ b/apps/gui/gwps.h @@ -23,7 +23,7 @@ #include "screen_access.h" #include "statusbar.h" -#include "id3.h" +#include "metadata.h" /* constants used in line_type and as refresh_mode for wps_refresh */ #define WPS_REFRESH_STATIC 1 /* line doesn't change over time */ diff --git a/apps/gui/statusbar.c b/apps/gui/statusbar.c index d426054..1a264ee 100644 --- a/apps/gui/statusbar.c +++ b/apps/gui/statusbar.c @@ -27,7 +27,7 @@ #include "sound.h" #include "settings.h" #if CONFIG_CODEC == SWCODEC -#include "id3.h" +#include "metadata.h" #endif #include "icons.h" #include "powermgmt.h" diff --git a/apps/id3.c b/apps/id3.c deleted file mode 100644 index c1541e3..0000000 --- a/apps/id3.c +++ /dev/null @@ -1,1353 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2002 by Daniel Stenberg - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * 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�deman. It has since been extended and enhanced pretty much by - * all sorts of friendly Rockbox people. - * - */ - - /* tagResolver and associated code copyright 2003 Thomas Paul Diffenbach - */ - -#include -#include -#include -#include -#include -#include -#include -#include "config.h" -#include "file.h" -#include "logf.h" - -#include "id3.h" -#include "mp3data.h" -#include "system.h" -#include "replaygain.h" -#include "rbunicode.h" - -/** Database of audio formats **/ -const struct afmt_entry audio_formats[AFMT_NUM_CODECS] = -{ - /* Unknown file format */ - [AFMT_UNKNOWN] = - AFMT_ENTRY("???", NULL, NULL, NULL ), - - /* MPEG Audio layer 1 */ - [AFMT_MPA_L1] = - AFMT_ENTRY("MP1", "mpa", NULL, "mp1\0" ), - /* MPEG Audio layer 2 */ - [AFMT_MPA_L2] = - AFMT_ENTRY("MP2", "mpa", NULL, "mpa\0mp2\0" ), - /* MPEG Audio layer 3 */ - [AFMT_MPA_L3] = - AFMT_ENTRY("MP3", "mpa", "mp3_enc", "mp3\0" ), - -#if CONFIG_CODEC == SWCODEC - /* Audio Interchange File Format */ - [AFMT_AIFF] = - AFMT_ENTRY("AIFF", "aiff", "aiff_enc", "aiff\0aif\0"), - /* Uncompressed PCM in a WAV file */ - [AFMT_PCM_WAV] = - AFMT_ENTRY("WAV", "wav", "wav_enc", "wav\0" ), - /* Ogg Vorbis */ - [AFMT_OGG_VORBIS] = - AFMT_ENTRY("Ogg", "vorbis", NULL, "ogg\0" ), - /* FLAC */ - [AFMT_FLAC] = - AFMT_ENTRY("FLAC", "flac", NULL, "flac\0" ), - /* Musepack */ - [AFMT_MPC] = - AFMT_ENTRY("MPC", "mpc", NULL, "mpc\0" ), - /* A/52 (aka AC3) audio */ - [AFMT_A52] = - AFMT_ENTRY("AC3", "a52", NULL, "a52\0ac3\0" ), - /* WavPack */ - [AFMT_WAVPACK] = - AFMT_ENTRY("WV", "wavpack", "wavpack_enc", "wv\0" ), - /* Apple Lossless Audio Codec */ - [AFMT_ALAC] = - AFMT_ENTRY("ALAC", "alac", NULL, "m4a\0m4b\0" ), - /* Advanced Audio Coding in M4A container */ - [AFMT_AAC] = - AFMT_ENTRY("AAC", "aac", NULL, "mp4\0" ), - /* Shorten */ - [AFMT_SHN] = - AFMT_ENTRY("SHN", "shorten", NULL, "shn\0" ), - /* SID File Format */ - [AFMT_SID] = - AFMT_ENTRY("SID", "sid", NULL, "sid\0" ), - /* ADX File Format */ - [AFMT_ADX] = - AFMT_ENTRY("ADX", "adx", NULL, "adx\0" ), - /* NESM (NES Sound Format) */ - [AFMT_NSF] = - AFMT_ENTRY("NSF", "nsf", NULL, "nsf\0nsfe\0" ), - /* Speex File Format */ - [AFMT_SPEEX] = - AFMT_ENTRY("Speex","speex", NULL, "spx\0" ), - /* SPC700 Save State */ - [AFMT_SPC] = - AFMT_ENTRY("SPC", "spc", NULL, "spc\0" ), - /* APE (Monkey's Audio) */ - [AFMT_APE] = - AFMT_ENTRY("APE", "ape", NULL, "ape\0mac\0" ), - /* WMA (WMAV1/V2 in ASF) */ - [AFMT_WMA] = - AFMT_ENTRY("WMA", "wma", NULL, "wma\0wmv\0asf\0" ), - /* Amiga MOD File */ - [AFMT_MOD] = - AFMT_ENTRY("MOD", "mod", NULL, "mod\0" ), - /* Amiga SAP File */ - [AFMT_SAP] = - AFMT_ENTRY("SAP", "asap", NULL, "sap\0" ), -#endif -}; - -#if CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING) -/* get REC_FORMAT_* corresponding AFMT_* */ -const int rec_format_afmt[REC_NUM_FORMATS] = -{ - /* give AFMT_UNKNOWN by default */ - [0 ... REC_NUM_FORMATS-1] = AFMT_UNKNOWN, - /* add new entries below this line */ - [REC_FORMAT_AIFF] = AFMT_AIFF, - [REC_FORMAT_MPA_L3] = AFMT_MPA_L3, - [REC_FORMAT_WAVPACK] = AFMT_WAVPACK, - [REC_FORMAT_PCM_WAV] = AFMT_PCM_WAV, -}; - -/* get AFMT_* corresponding REC_FORMAT_* */ -const int afmt_rec_format[AFMT_NUM_CODECS] = -{ - /* give -1 by default */ - [0 ... AFMT_NUM_CODECS-1] = -1, - /* add new entries below this line */ - [AFMT_AIFF] = REC_FORMAT_AIFF, - [AFMT_MPA_L3] = REC_FORMAT_MPA_L3, - [AFMT_WAVPACK] = REC_FORMAT_WAVPACK, - [AFMT_PCM_WAV] = REC_FORMAT_PCM_WAV, -}; -#endif /* CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING) */ -/****/ - -static unsigned long unsync(unsigned long b0, - unsigned long b1, - unsigned long b2, - unsigned long b3) -{ - return (((long)(b0 & 0x7F) << (3*7)) | - ((long)(b1 & 0x7F) << (2*7)) | - ((long)(b2 & 0x7F) << (1*7)) | - ((long)(b3 & 0x7F) << (0*7))); -} - -static const char* const genres[] = { - "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", - "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", - "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", - "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", - "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", - "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", - "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", - "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", - "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", - "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", - "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", - "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", - "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", - - /* winamp extensions */ - "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", - "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", - "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", - "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", - "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", - "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", - "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", - "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", - "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", - "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", - "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", - "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "Jpop", - "Synthpop" -}; - -char* id3_get_num_genre(unsigned int genre_num) -{ - if (genre_num < sizeof(genres)/sizeof(char*)) - return (char*)genres[genre_num]; - return NULL; -} - -/* True if the string is from the "genres" array */ -static bool id3_is_genre_string(const char *string) -{ - return ( string >= genres[0] && - string <= genres[sizeof(genres)/sizeof(char*) - 1] ); -} - -/* - HOW TO ADD ADDITIONAL ID3 VERSION 2 TAGS - Code and comments by Thomas Paul Diffenbach - - To add another ID3v2 Tag, do the following: - 1. add a char* named for the tag to struct mp3entry in id3.h, - (I (tpd) prefer to use char* rather than ints, even for what seems like - numerical values, for cases where a number won't do, e.g., - YEAR: "circa 1765", "1790/1977" (composed/performed), "28 Feb 1969" - TRACK: "1/12", "1 of 12", GENRE: "Freeform genre name" - Text is more flexible, and as the main use of id3 data is to - display it, converting it to an int just means reconverting to - display it, at a runtime cost.) - - 2. If any special processing beyond copying the tag value from the Id3 - block to the struct mp3entry is rrequired (such as converting to an - int), write a function to perform this special processing. - - This function's prototype must match that of - typedef tagPostProcessFunc, that is it must be: - int func( struct mp3entry*, char* tag, int bufferpos ) - the first argument is a pointer to the current mp3entry structure the - second argument is a pointer to the null terminated string value of the - tag found the third argument is the offset of the next free byte in the - mp3entry's buffer your function should return the corrected offset; if - you don't lengthen or shorten the tag string, you can return the third - argument unchanged. - - Unless you have a good reason no to, make the function static. - TO JUST COPY THE TAG NO SPECIAL PROCESSING FUNCTION IS NEEDED. - - 3. add one or more entries to the tagList array, using the format: - char* ID3 Tag symbolic name -- see the ID3 specification for these, - sizeof() that name minus 1, - offsetof( struct mp3entry, variable_name_in_struct_mp3entry ), - pointer to your special processing function or NULL - if you need no special processing - flag indicating if this tag is binary or textual - Many ID3 symbolic names come in more than one form. You can add both - forms, each referencing the same variable in struct mp3entry. - If both forms are present, the last found will be used. - Note that the offset can be zero, in which case no entry will be set - in the mp3entry struct; the frame is still read into the buffer and - the special processing function is called (several times, if there - are several frames with the same name). - - 4. Alternately, use the TAG_LIST_ENTRY macro with - ID3 tag symbolic name, - variable in struct mp3entry, - special processing function address - - 5. Add code to wps-display.c function get_tag to assign a printf-like - format specifier for the tag */ - -/* Structure for ID3 Tag extraction information */ -struct tag_resolver { - const char* tag; - int tag_length; - size_t offset; - int (*ppFunc)(struct mp3entry*, char* tag, int bufferpos); - bool binary; -}; - -static bool global_ff_found; - -static int unsynchronize(char* tag, int len, bool *ff_found) -{ - int i; - unsigned char c; - unsigned char *rp, *wp; - - wp = rp = (unsigned char *)tag; - - rp = (unsigned char *)tag; - for(i = 0;i < len;i++) { - /* Read the next byte and write it back, but don't increment the - write pointer */ - c = *rp++; - *wp = c; - if(*ff_found) { - /* Increment the write pointer if it isn't an unsynch pattern */ - if(c != 0) - wp++; - *ff_found = false; - } else { - if(c == 0xff) - *ff_found = true; - wp++; - } - } - return (long)wp - (long)tag; -} - -static int unsynchronize_frame(char* tag, int len) -{ - bool ff_found = false; - - return unsynchronize(tag, len, &ff_found); -} - -static int read_unsynched(int fd, void *buf, int len) -{ - int i; - int rc; - int remaining = len; - char *wp; - char *rp; - - wp = buf; - - while(remaining) { - rp = wp; - rc = read(fd, rp, remaining); - if(rc <= 0) - return rc; - - i = unsynchronize(wp, remaining, &global_ff_found); - remaining -= i; - wp += i; - } - - return len; -} - -static int skip_unsynched(int fd, int len) -{ - int rc; - int remaining = len; - int rlen; - char buf[32]; - - while(remaining) { - rlen = MIN(sizeof(buf), (unsigned int)remaining); - rc = read(fd, buf, rlen); - if(rc <= 0) - return rc; - - remaining -= unsynchronize(buf, rlen, &global_ff_found); - } - - return len; -} - -/* parse numeric value from string */ -static int parsetracknum( struct mp3entry* entry, char* tag, int bufferpos ) -{ - entry->tracknum = atoi( tag ); - return bufferpos; -} - -/* parse numeric value from string */ -static int parsediscnum( struct mp3entry* entry, char* tag, int bufferpos ) -{ - entry->discnum = atoi( tag ); - return bufferpos; -} - -/* parse numeric value from string */ -static int parseyearnum( struct mp3entry* entry, char* tag, int bufferpos ) -{ - entry->year = atoi( tag ); - return bufferpos; -} - -/* parse numeric genre from string, version 2.2 and 2.3 */ -static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos ) -{ - if(entry->id3version >= ID3_VER_2_4) { - /* In version 2.4 and up, there are no parentheses, and the genre frame - is a list of strings, either numbers or text. */ - - /* Is it a number? */ - if(isdigit(tag[0])) { - entry->genre_string = id3_get_num_genre(atoi( tag )); - return tag - entry->id3v2buf; - } else { - entry->genre_string = tag; - return bufferpos; - } - } else { - if( tag[0] == '(' && tag[1] != '(' ) { - entry->genre_string = id3_get_num_genre(atoi( tag + 1 )); - return tag - entry->id3v2buf; - } - else { - entry->genre_string = tag; - return bufferpos; - } - } -} - -#if CONFIG_CODEC == SWCODEC -/* parse user defined text, looking for replaygain information. */ -static int parseuser( struct mp3entry* entry, char* tag, int bufferpos ) -{ - char* value = NULL; - int desc_len = strlen(tag); - int value_len = 0; - - if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) { - /* At least part of the value was read, so we can safely try to - * parse it - */ - value = tag + desc_len + 1; - value_len = parse_replaygain(tag, value, entry, tag, - bufferpos - (tag - entry->id3v2buf)); - } - - return tag - entry->id3v2buf + value_len; -} - -/* parse RVA2 binary data and convert to replaygain information. */ -static int parserva2( struct mp3entry* entry, char* tag, int bufferpos ) -{ - int desc_len = strlen(tag); - int start_pos = tag - entry->id3v2buf; - int end_pos = start_pos + desc_len + 5; - int value_len = 0; - unsigned char* value = tag + desc_len + 1; - - /* Only parse RVA2 replaygain tags if tag version == 2.4 and channel - * type is master volume. - */ - if (entry->id3version == ID3_VER_2_4 && end_pos < bufferpos - && *value++ == 1) { - long gain = 0; - long peak = 0; - long peakbits; - long peakbytes; - bool album = false; - - /* The RVA2 specification is unclear on some things (id string and - * peak volume), but this matches how Quod Libet use them. - */ - - gain = (int16_t) ((value[0] << 8) | value[1]); - value += 2; - peakbits = *value++; - peakbytes = (peakbits + 7) / 8; - - /* Only use the topmost 24 bits for peak volume */ - if (peakbytes > 3) { - peakbytes = 3; - } - - /* Make sure the peak bits were read */ - if (end_pos + peakbytes < bufferpos) { - long shift = ((8 - (peakbits & 7)) & 7) + (3 - peakbytes) * 8; - - for ( ; peakbytes; peakbytes--) { - peak <<= 8; - peak += *value++; - } - - peak <<= shift; - - if (peakbits > 24) { - peak += *value >> (8 - shift); - } - } - - if (strcasecmp(tag, "album") == 0) { - album = true; - } else if (strcasecmp(tag, "track") != 0) { - /* Only accept non-track values if we don't have any previous - * value. - */ - if (entry->track_gain != 0) { - return start_pos; - } - } - - value_len = parse_replaygain_int(album, gain, peak * 2, entry, - tag, sizeof(entry->id3v2buf) - start_pos); - } - - return start_pos + value_len; -} -#endif - -static int parsembtid( struct mp3entry* entry, char* tag, int bufferpos ) -{ - char* value = NULL; - int desc_len = strlen(tag); - /*DEBUGF("MBID len: %d\n", desc_len);*/ - int value_len = 0; - - if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) - { - value = tag + desc_len + 1; - - if (strcasecmp(tag, "http://musicbrainz.org") == 0) - { - /* Musicbrainz track IDs are always 36 chars long plus null */ - value_len = 37; - - entry->mb_track_id = value; - - /*DEBUGF("ENTRY: %s LEN: %d\n", entry->mb_track_id, strlen(entry->mb_track_id));*/ - } - } - - return tag - entry->id3v2buf + value_len; -} - -static const struct tag_resolver taglist[] = { - { "TPE1", 4, offsetof(struct mp3entry, artist), NULL, false }, - { "TP1", 3, offsetof(struct mp3entry, artist), NULL, false }, - { "TIT2", 4, offsetof(struct mp3entry, title), NULL, false }, - { "TT2", 3, offsetof(struct mp3entry, title), NULL, false }, - { "TALB", 4, offsetof(struct mp3entry, album), NULL, false }, - { "TAL", 3, offsetof(struct mp3entry, album), NULL, false }, - { "TRK", 3, offsetof(struct mp3entry, track_string), &parsetracknum, false }, - { "TPOS", 4, offsetof(struct mp3entry, disc_string), &parsediscnum, false }, - { "TRCK", 4, offsetof(struct mp3entry, track_string), &parsetracknum, false }, - { "TDRC", 4, offsetof(struct mp3entry, year_string), &parseyearnum, false }, - { "TYER", 4, offsetof(struct mp3entry, year_string), &parseyearnum, false }, - { "TYE", 3, offsetof(struct mp3entry, year_string), &parseyearnum, false }, - { "TCOM", 4, offsetof(struct mp3entry, composer), NULL, false }, - { "TPE2", 4, offsetof(struct mp3entry, albumartist), NULL, false }, - { "TP2", 3, offsetof(struct mp3entry, albumartist), NULL, false }, - { "TIT1", 4, offsetof(struct mp3entry, grouping), NULL, false }, - { "TT1", 3, offsetof(struct mp3entry, grouping), NULL, false }, - { "COMM", 4, offsetof(struct mp3entry, comment), NULL, false }, - { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre, false }, - { "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre, false }, -#if CONFIG_CODEC == SWCODEC - { "TXXX", 4, 0, &parseuser, false }, - { "RVA2", 4, 0, &parserva2, true }, -#endif - { "UFID", 4, 0, &parsembtid, false }, -}; - -#define TAGLIST_SIZE ((int)(sizeof(taglist) / sizeof(taglist[0]))) - -/* Get the length of an ID3 string in the given encoding. Returns the length - * in bytes, including end nil, or -1 if the encoding is unknown. - */ -static int unicode_len(char encoding, const void* string) -{ - int len = 0; - - if (encoding == 0x01 || encoding == 0x02) { - char first; - const char *s = string; - /* string might be unaligned, so using short* can crash on ARM and SH1 */ - do { - first = *s++; - } while ((first | *s++) != 0); - - len = s - (const char*) string; - } else { - len = strlen((char*) string) + 1; - } - - return len; -} - -/* Checks to see if the passed in string is a 16-bit wide Unicode v2 - string. If it is, we convert it to a UTF-8 string. If it's not unicode, - we convert from the default codepage */ -static int unicode_munge(char* string, char* utf8buf, int *len) { - long tmp; - bool le = false; - int i = 0; - unsigned char *str = (unsigned char *)string; - int templen = 0; - unsigned char* utf8 = (unsigned char *)utf8buf; - - switch (str[0]) { - case 0x00: /* Type 0x00 is ordinary ISO 8859-1 */ - str++; - (*len)--; - utf8 = iso_decode(str, utf8, -1, *len); - *utf8 = 0; - *len = (unsigned long)utf8 - (unsigned long)utf8buf; - break; - - case 0x01: /* Unicode with or without BOM */ - case 0x02: - (*len)--; - str++; - - /* Handle frames with more than one string - (needed for TXXX frames).*/ - do { - tmp = bytes2int(0, 0, str[0], str[1]); - - /* Now check if there is a BOM - (zero-width non-breaking space, 0xfeff) - and if it is in little or big endian format */ - if(tmp == 0xfffe) { /* Little endian? */ - le = true; - str += 2; - (*len)-=2; - } else if(tmp == 0xfeff) { /* Big endian? */ - str += 2; - (*len)-=2; - } else - /* If there is no BOM (which is a specification violation), - let's try to guess it. If one of the bytes is 0x00, it is - probably the most significant one. */ - if(str[1] == 0) - le = true; - - do { - if(le) - utf8 = utf16LEdecode(str, utf8, 1); - else - utf8 = utf16BEdecode(str, utf8, 1); - - str+=2; - i += 2; - } while((str[0] || str[1]) && (i < *len)); - - *utf8++ = 0; /* Terminate the string */ - templen += (strlen(&utf8buf[templen]) + 1); - str += 2; - i+=2; - } while(i < *len); - *len = templen - 1; - break; - - case 0x03: /* UTF-8 encoded string */ - for(i=0; i < *len; i++) - utf8[i] = str[i+1]; - (*len)--; - break; - - default: /* Plain old string */ - utf8 = iso_decode(str, utf8, -1, *len); - *utf8 = 0; - *len = (unsigned long)utf8 - (unsigned long)utf8buf; - break; - } - return 0; -} - -/* - * Sets the title of an MP3 entry based on its ID3v1 tag. - * - * Arguments: file - the MP3 file to scen for a ID3v1 tag - * entry - the entry to set the title in - * - * Returns: true if a title was found and created, else false - */ -static bool setid3v1title(int fd, struct mp3entry *entry) -{ - unsigned char buffer[128]; - static const char offsets[] = {3, 33, 63, 97, 93, 125, 127}; - int i, j; - unsigned char* utf8; - - if (-1 == lseek(fd, -128, SEEK_END)) - return false; - - if (read(fd, buffer, sizeof buffer) != sizeof buffer) - return false; - - if (strncmp((char *)buffer, "TAG", 3)) - return false; - - entry->id3v1len = 128; - entry->id3version = ID3_VER_1_0; - - for (i=0; i < (int)sizeof offsets; i++) { - unsigned char* ptr = (unsigned char *)buffer + offsets[i]; - - switch(i) { - case 0: - case 1: - case 2: - /* kill trailing space in strings */ - for (j=29; j && (ptr[j]==0 || ptr[j]==' '); j--) - ptr[j] = 0; - /* convert string to utf8 */ - utf8 = (unsigned char *)entry->id3v1buf[i]; - utf8 = iso_decode(ptr, utf8, -1, 30); - /* make sure string is terminated */ - *utf8 = 0; - break; - - case 3: - /* kill trailing space in strings */ - for (j=27; j && (ptr[j]==0 || ptr[j]==' '); j--) - ptr[j] = 0; - /* convert string to utf8 */ - utf8 = (unsigned char *)entry->id3v1buf[3]; - utf8 = iso_decode(ptr, utf8, -1, 28); - /* make sure string is terminated */ - *utf8 = 0; - break; - - case 4: - ptr[4] = 0; - entry->year = atoi((char *)ptr); - break; - - case 5: - /* id3v1.1 uses last two bytes of comment field for track - number: first must be 0 and second is track num */ - if (!ptr[0] && ptr[1]) { - entry->tracknum = ptr[1]; - entry->id3version = ID3_VER_1_1; - } - break; - - case 6: - /* genre */ - entry->genre_string = id3_get_num_genre(ptr[0]); - break; - } - } - - entry->title = entry->id3v1buf[0]; - entry->artist = entry->id3v1buf[1]; - entry->album = entry->id3v1buf[2]; - entry->comment = entry->id3v1buf[3]; - - return true; -} - - -/* - * Sets the title of an MP3 entry based on its ID3v2 tag. - * - * Arguments: file - the MP3 file to scan for a ID3v2 tag - * entry - the entry to set the title in - * - * Returns: true if a title was found and created, else false - */ -static void setid3v2title(int fd, struct mp3entry *entry) -{ - int minframesize; - int size; - long bufferpos = 0, totframelen, framelen; - char header[10]; - char tmp[4]; - unsigned char version; - char *buffer = entry->id3v2buf; - int bytesread = 0; - int buffersize = sizeof(entry->id3v2buf); - unsigned char global_flags; - int flags; - int skip; - bool global_unsynch = false; - bool unsynch = false; - int i, j; - int rc; - - global_ff_found = false; - - /* Bail out if the tag is shorter than 10 bytes */ - if(entry->id3v2len < 10) - return; - - /* Read the ID3 tag version from the header */ - lseek(fd, 0, SEEK_SET); - if(10 != read(fd, header, 10)) - return; - - /* Get the total ID3 tag size */ - size = entry->id3v2len - 10; - - version = header[3]; - switch ( version ) { - case 2: - version = ID3_VER_2_2; - minframesize = 8; - break; - - case 3: - version = ID3_VER_2_3; - minframesize = 12; - break; - - case 4: - version = ID3_VER_2_4; - minframesize = 12; - break; - - default: - /* unsupported id3 version */ - return; - } - entry->id3version = version; - entry->tracknum = entry->year = entry->discnum = 0; - entry->title = entry->artist = entry->album = NULL; /* FIXME incomplete */ - - global_flags = header[5]; - - /* Skip the extended header if it is present */ - if(global_flags & 0x40) { - if(version == ID3_VER_2_3) { - if(10 != read(fd, header, 10)) - return; - /* The 2.3 extended header size doesn't include the header size - field itself. Also, it is not unsynched. */ - framelen = - bytes2int(header[0], header[1], header[2], header[3]) + 4; - - /* Skip the rest of the header */ - lseek(fd, framelen - 10, SEEK_CUR); - } - - if(version >= ID3_VER_2_4) { - if(4 != read(fd, header, 4)) - return; - - /* The 2.4 extended header size does include the entire header, - so here we can just skip it. This header is unsynched. */ - framelen = unsync(header[0], header[1], - header[2], header[3]); - - lseek(fd, framelen - 4, SEEK_CUR); - } - } - - /* Is unsynchronization applied? */ - if(global_flags & 0x80) { - global_unsynch = true; - } - - /* - * We must have at least minframesize bytes left for the - * remaining frames to be interesting - */ - while (size >= minframesize && bufferpos < buffersize - 1) { - flags = 0; - - /* Read frame header and check length */ - if(version >= ID3_VER_2_3) { - if(global_unsynch && version <= ID3_VER_2_3) - rc = read_unsynched(fd, header, 10); - else - rc = read(fd, header, 10); - if(rc != 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], - header[6], header[7]); - } else { - /* version .3 files don't use synchsafe ints for - * size */ - framelen = bytes2int(header[4], header[5], - header[6], header[7]); - } - } else { - if(6 != read(fd, header, 6)) - return; - /* Adjust for the 6 bytes we read */ - size -= 6; - - framelen = bytes2int(0, header[3], header[4], header[5]); - } - - logf("framelen = %ld", framelen); - if(framelen == 0){ - if (header[0] == 0 && header[1] == 0 && header[2] == 0) - return; - else - continue; - } - - unsynch = false; - - if(flags) - { - skip = 0; - - if (version >= ID3_VER_2_4) { - if(flags & 0x0040) { /* Grouping identity */ - lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */ - framelen--; - } - } else { - if(flags & 0x0020) { /* Grouping identity */ - lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */ - framelen--; - } - } - - if(flags & 0x000c) /* Compression or encryption */ - { - /* Skip it */ - size -= framelen; - lseek(fd, framelen, SEEK_CUR); - continue; - } - - if(flags & 0x0002) /* Unsynchronization */ - unsynch = true; - - if (version >= ID3_VER_2_4) { - if(flags & 0x0001) { /* Data length indicator */ - if(4 != read(fd, tmp, 4)) - return; - - /* We don't need the data length */ - framelen -= 4; - } - } - } - - /* Keep track of the remaining frame size */ - totframelen = framelen; - - /* 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) - framelen = buffersize - bufferpos - 1; - - logf("id3v2 frame: %.4s", header); - - /* Check for certain frame headers - - 'size' is the amount of frame bytes remaining. We decrement it by - the amount of bytes we read. If we fail to read as many bytes as - we expect, we assume that we can't read from this file, and bail - out. - - For each frame. we will iterate over the list of supported tags, - and read the tag into entry's buffer. All tags will be kept as - strings, for cases where a number won't do, e.g., YEAR: "circa - 1765", "1790/1977" (composed/performed), "28 Feb 1969" TRACK: - "1/12", "1 of 12", GENRE: "Freeform genre name" Text is more - flexible, and as the main use of id3 data is to display it, - converting it to an int just means reconverting to display it, at a - runtime cost. - - For tags that the current code does convert to ints, a post - processing function will be called via a pointer to function. */ - - for (i=0; ioffset ? (char**) (((char*)entry) + tr->offset) - : NULL; - char* tag; - - /* Only ID3_VER_2_2 uses frames with three-character names. */ - if (((version == ID3_VER_2_2) && (tr->tag_length != 3)) - || ((version > ID3_VER_2_2) && (tr->tag_length != 4))) { - continue; - } - - /* Note that parser functions sometimes set *ptag to NULL, so - * the "!*ptag" check here doesn't always have the desired - * effect. Should the parser functions (parsegenre in - * particular) be updated to handle the case of being called - * multiple times, or should the "*ptag" check be removed? - */ - if( (!ptag || !*ptag) && !memcmp( header, tr->tag, tr->tag_length ) ) { - - /* found a tag matching one in tagList, and not yet filled */ - tag = buffer + bufferpos; - - if(global_unsynch && version <= ID3_VER_2_3) - bytesread = read_unsynched(fd, tag, framelen); - else - bytesread = read(fd, tag, framelen); - - if( bytesread != framelen ) - return; - - size -= bytesread; - - if(unsynch || (global_unsynch && version >= ID3_VER_2_4)) - bytesread = unsynchronize_frame(tag, bytesread); - - /* the COMM frame has a 3 char field to hold an ISO-639-1 - * language string and an optional short description; - * remove them so unicode_munge can work correctly - */ - - if(!memcmp( header, "COMM", 4 )) { - int offset; - /* ignore comments with iTunes 7 soundcheck/gapless data */ - if(!strncmp(tag+4, "iTun", 4)) - break; - offset = 3 + unicode_len(*tag, tag + 4); - if(bytesread > offset) { - bytesread -= offset; - memmove(tag + 1, tag + 1 + offset, bytesread - 1); - } - } - - /* Attempt to parse Unicode string only if the tag contents - aren't binary */ - if(!tr->binary) { - /* UTF-8 could potentially be 3 times larger */ - /* so we need to create a new buffer */ - char utf8buf[(3 * bytesread) + 1]; - - unicode_munge( tag, utf8buf, &bytesread ); - - if(bytesread >= buffersize - bufferpos) - bytesread = buffersize - bufferpos - 1; - - for (j = 0; j < bytesread; j++) - tag[j] = utf8buf[j]; - - /* remove trailing spaces */ - while ( bytesread > 0 && isspace(tag[bytesread-1])) - bytesread--; - } - - tag[bytesread] = 0; - bufferpos += bytesread + 1; - - if (ptag) - *ptag = tag; - - if( tr->ppFunc ) - bufferpos = tr->ppFunc(entry, tag, bufferpos); - - /* Seek to the next frame */ - if(framelen < totframelen) - lseek(fd, totframelen - framelen, SEEK_CUR); - break; - } - } - - if( i == TAGLIST_SIZE ) { - /* no tag in tagList was found, or it was a repeat. - skip it using the total size */ - - if(global_unsynch && version <= ID3_VER_2_3) { - size -= skip_unsynched(fd, totframelen); - } else { - size -= totframelen; - if( lseek(fd, totframelen, SEEK_CUR) == -1 ) - return; - } - } - } -} - -/* - * Calculates the size of the ID3v2 tag. - * - * Arguments: file - the file to search for a tag. - * - * Returns: the size of the tag or 0 if none was found - */ -int getid3v2len(int fd) -{ - char buf[6]; - int offset; - - /* Make sure file has a ID3 tag */ - if((-1 == lseek(fd, 0, SEEK_SET)) || - (read(fd, buf, 6) != 6) || - (strncmp(buf, "ID3", strlen("ID3")) != 0)) - offset = 0; - - /* Now check what the ID3v2 size field says */ - else - if(read(fd, buf, 4) != 4) - offset = 0; - else - offset = unsync(buf[0], buf[1], buf[2], buf[3]) + 10; - - logf("ID3V2 Length: 0x%x", offset); - return offset; -} - -/* - * Calculates the length (in milliseconds) of an MP3 file. - * - * Modified to only use integers. - * - * Arguments: file - the file to calculate the length upon - * entry - the entry to update with the length - * - * Returns: the song length in milliseconds, - * 0 means that it couldn't be calculated - */ -static int getsonglength(int fd, struct mp3entry *entry) -{ - unsigned long filetime = 0; - struct mp3info info; - long bytecount; - - /* Start searching after ID3v2 header */ - if(-1 == lseek(fd, entry->id3v2len, SEEK_SET)) - return 0; - - bytecount = get_mp3file_info(fd, &info); - - logf("Space between ID3V2 tag and first audio frame: 0x%lx bytes", - bytecount); - - if(bytecount < 0) - return -1; - - bytecount += entry->id3v2len; - - /* Validate byte count, in case the file has been edited without - * updating the header. - */ - if (info.byte_count) - { - const unsigned long expected = entry->filesize - entry->id3v1len - - entry->id3v2len; - const unsigned long diff = MAX(10240, info.byte_count / 20); - - if ((info.byte_count > expected + diff) - || (info.byte_count < expected - diff)) - { - logf("Note: info.byte_count differs from expected value by " - "%ld bytes", labs((long) (expected - info.byte_count))); - info.byte_count = 0; - info.frame_count = 0; - info.file_time = 0; - info.enc_padding = 0; - - /* Even if the bitrate was based on "known bad" values, it - * should still be better for VBR files than using the bitrate - * of the first audio frame. - */ - } - } - - entry->bitrate = info.bitrate; - entry->frequency = info.frequency; - entry->version = info.version; - entry->layer = info.layer; - switch(entry->layer) { -#if CONFIG_CODEC==SWCODEC - case 0: - entry->codectype=AFMT_MPA_L1; - break; -#endif - case 1: - entry->codectype=AFMT_MPA_L2; - break; - case 2: - entry->codectype=AFMT_MPA_L3; - break; - } - - /* 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) - { - /* Prevent a division by zero */ - if (info.bitrate < 8) - filetime = 0; - else - filetime = (entry->filesize - bytecount) / (info.bitrate / 8); - /* bitrate is in kbps so this delivers milliseconds. Doing bitrate / 8 - * instead of filesize * 8 is exact, because mpeg audio bitrates are - * always multiples of 8, and it avoids overflows. */ - } - - entry->frame_count = info.frame_count; - - entry->vbr = info.is_vbr; - entry->has_toc = info.has_toc; - -#if CONFIG_CODEC==SWCODEC - entry->lead_trim = info.enc_delay; - entry->tail_trim = info.enc_padding; -#endif - - memcpy(entry->toc, info.toc, sizeof(info.toc)); - - entry->vbr_header_pos = info.vbr_header_pos; - - /* Update the seek point for the first playable frame */ - entry->first_frame_offset = bytecount; - logf("First frame is at %lx", entry->first_frame_offset); - - return filetime; -} - -/* - * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc) - * about an MP3 file and updates it's entry accordingly. - * - Note, that this returns true for successful, false for error! */ -bool get_mp3_metadata(int fd, struct mp3entry *entry, const char *filename) -{ -#if CONFIG_CODEC != SWCODEC - memset(entry, 0, sizeof(struct mp3entry)); -#endif - - strncpy(entry->path, filename, sizeof(entry->path)); - - entry->title = NULL; - entry->filesize = filesize(fd); - entry->id3v2len = getid3v2len(fd); - entry->tracknum = 0; - entry->discnum = 0; - - if (entry->id3v2len) - setid3v2title(fd, entry); - int len = getsonglength(fd, entry); - if (len < 0) - return false; - entry->length = len; - - /* 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) { - setid3v1title(fd, entry); - } - - if(!entry->length || (entry->filesize < 8 )) - /* no song length or less than 8 bytes is hereby considered to be an - invalid mp3 and won't be played by us! */ - return false; - - return true; -} - -/* Note, that this returns false for successful, true for error! */ -bool mp3info(struct mp3entry *entry, const char *filename) -{ - int fd; - bool result; - - fd = open(filename, O_RDONLY); - if (fd < 0) - return true; - - result = !get_mp3_metadata(fd, entry, filename); - - close(fd); - - return result; -} - -void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig) -{ - long offset; - if (orig > dest) - offset = - ((size_t)orig - (size_t)dest); - else - offset = (size_t)dest - (size_t)orig; - - if (entry->title) - entry->title += offset; - if (entry->artist) - entry->artist += offset; - if (entry->album) - entry->album += offset; - if (entry->genre_string && !id3_is_genre_string(entry->genre_string)) - /* Don't adjust that if it points to an entry of the "genres" array */ - entry->genre_string += offset; - if (entry->track_string) - entry->track_string += offset; - if (entry->disc_string) - entry->disc_string += offset; - if (entry->year_string) - entry->year_string += offset; - if (entry->composer) - entry->composer += offset; - if (entry->comment) - entry->comment += offset; - if (entry->albumartist) - entry->albumartist += offset; - if (entry->grouping) - entry->grouping += offset; -#if CONFIG_CODEC == SWCODEC - if (entry->track_gain_string) - entry->track_gain_string += offset; - if (entry->album_gain_string) - entry->album_gain_string += offset; -#endif - if (entry->mb_track_id) - entry->mb_track_id += offset; -} - -void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig) -{ - memcpy(dest, orig, sizeof(struct mp3entry)); - adjust_mp3entry(dest, dest, orig); -} - -#ifdef DEBUG_STANDALONE - -char *secs2str(int ms) -{ - static char buffer[32]; - int secs = ms/1000; - ms %= 1000; - snprintf(buffer, sizeof(buffer), "%d:%02d.%d", secs/60, secs%60, ms/100); - return buffer; -} - -int main(int argc, char **argv) -{ - int i; - for(i=1; i", - mp3.artist?mp3.artist:"", - mp3.album?mp3.album:"", - mp3.genre_string?mp3.genre_string:"", - mp3.genre, - mp3.composer?mp3.composer:"", - mp3.year_string?mp3.year_string:"", - mp3.year, - mp3.track_string?mp3.track_string:"", - mp3.tracknum, - secs2str(mp3.length), - mp3.length/1000, - mp3.bitrate, - mp3.frequency); - } - - return 0; -} - -#endif diff --git a/apps/id3.h b/apps/id3.h deleted file mode 100644 index da2faf1..0000000 --- a/apps/id3.h +++ /dev/null @@ -1,246 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2002 by Daniel Stenberg - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ -#ifndef ID3_H -#define ID3_H - -#include -#include "config.h" -#include "file.h" - -#define ID3V2_BUF_SIZE 300 - -/* Audio file types. */ -/* NOTE: The values of the AFMT_* items are used for the %fc tag in the WPS - - so new entries MUST be added to the end to maintain compatibility. - */ -enum -{ - AFMT_UNKNOWN = 0, /* Unknown file format */ - - /* start formats */ - - AFMT_MPA_L1, /* MPEG Audio layer 1 */ - AFMT_MPA_L2, /* MPEG Audio layer 2 */ - AFMT_MPA_L3, /* MPEG Audio layer 3 */ - -#if CONFIG_CODEC == SWCODEC - AFMT_AIFF, /* Audio Interchange File Format */ - AFMT_PCM_WAV, /* Uncompressed PCM in a WAV file */ - AFMT_OGG_VORBIS, /* Ogg Vorbis */ - AFMT_FLAC, /* FLAC */ - AFMT_MPC, /* Musepack */ - AFMT_A52, /* A/52 (aka AC3) audio */ - AFMT_WAVPACK, /* WavPack */ - AFMT_ALAC, /* Apple Lossless Audio Codec */ - AFMT_AAC, /* Advanced Audio Coding (AAC) in M4A container */ - AFMT_SHN, /* Shorten */ - AFMT_SID, /* SID File Format */ - AFMT_ADX, /* ADX File Format */ - AFMT_NSF, /* NESM (NES Sound Format) */ - AFMT_SPEEX, /* Ogg Speex speech */ - AFMT_SPC, /* SPC700 save state */ - AFMT_APE, /* Monkey's Audio (APE) */ - AFMT_WMA, /* WMAV1/V2 in ASF */ - AFMT_MOD, /* Amiga MOD File Format */ - AFMT_SAP, /* Amiga 8Bit SAP Format */ -#endif - - /* add new formats at any index above this line to have a sensible order - - specified array index inits are used */ - /* format arrays defined in id3.c */ - - AFMT_NUM_CODECS, - -#if CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) - /* masks to decompose parts */ - CODEC_AFMT_MASK = 0x0fff, - CODEC_TYPE_MASK = 0x7000, - - /* switch for specifying codec type when requesting a filename */ - CODEC_TYPE_DECODER = (0 << 12), /* default */ - CODEC_TYPE_ENCODER = (1 << 12), -#endif /* CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) */ -}; - -#if CONFIG_CODEC == SWCODEC -#define CODEC_EXTENSION "codec" - -#ifdef HAVE_RECORDING -#define ENCODER_SUFFIX "_enc" -enum rec_format_indexes -{ - __REC_FORMAT_START_INDEX = -1, - - /* start formats */ - - REC_FORMAT_PCM_WAV, - REC_FORMAT_AIFF, - REC_FORMAT_WAVPACK, - REC_FORMAT_MPA_L3, - - /* add new formats at any index above this line to have a sensible order - - specified array index inits are used - REC_FORMAT_CFG_NUM_BITS should allocate enough bits to hold the range - REC_FORMAT_CFG_VALUE_LIST should be in same order as indexes - */ - - REC_NUM_FORMATS, - - REC_FORMAT_DEFAULT = REC_FORMAT_PCM_WAV, - REC_FORMAT_CFG_NUM_BITS = 2 -}; - -#define REC_FORMAT_CFG_VAL_LIST "wave,aiff,wvpk,mpa3" - -/* get REC_FORMAT_* corresponding AFMT_* */ -extern const int rec_format_afmt[REC_NUM_FORMATS]; -/* get AFMT_* corresponding REC_FORMAT_* */ -extern const int afmt_rec_format[AFMT_NUM_CODECS]; - -#define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \ - { label, root_fname, enc_root_fname, ext_list } -#else /* !HAVE_RECORDING */ -#define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \ - { label, root_fname, ext_list } -#endif /* HAVE_RECORDING */ -#else /* !SWCODEC */ - -#define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \ - { label, ext_list } -#endif /* CONFIG_CODEC == SWCODEC */ - -/* record describing the audio format */ -struct afmt_entry -{ - char label[8]; /* format label */ -#if CONFIG_CODEC == SWCODEC - char *codec_root_fn; /* root codec filename (sans _enc and .codec) */ -#ifdef HAVE_RECORDING - char *codec_enc_root_fn; /* filename of encoder codec */ -#endif -#endif - char *ext_list; /* double NULL terminated extension - list for type with the first as - the default for recording */ -}; - -/* database of labels and codecs. add formats per above enum */ -extern const struct afmt_entry audio_formats[AFMT_NUM_CODECS]; - -struct mp3entry { - char path[MAX_PATH]; - char* title; - char* artist; - char* album; - char* genre_string; - char* disc_string; - char* track_string; - char* year_string; - char* composer; - char* comment; - char* albumartist; - char* grouping; - int discnum; - int tracknum; - int version; - int layer; - int year; - unsigned char id3version; - unsigned int codectype; - unsigned int bitrate; - 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 */ - - /* Added for Vorbis */ - unsigned long samples; /* number of samples in track */ - - /* MP3 stream specific info */ - unsigned long frame_count; /* number of frames in the file (if VBR) */ - - /* Used for A52/AC3 */ - unsigned long bytesperframe; /* number of bytes per frame (if CBR) */ - - /* 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 */ - - /* these following two fields are used for local buffering */ - char id3v2buf[ID3V2_BUF_SIZE]; - char id3v1buf[4][92]; - - /* resume related */ - unsigned long offset; /* bytes played */ - int index; /* playlist index */ - - /* runtime database fields */ - long tagcache_idx; /* 0=invalid, otherwise idx+1 */ - int rating; - int score; - long playcount; - long lastplayed; - long playtime; - - /* replaygain support */ - -#if CONFIG_CODEC == SWCODEC - char* track_gain_string; - char* album_gain_string; - long track_gain; /* 7.24 signed fixed point. 0 for no gain. */ - long album_gain; - long track_peak; /* 7.24 signed fixed point. 0 for no peak. */ - long album_peak; -#endif - - /* Cuesheet support */ - int cuesheet_type; /* 0: none, 1: external, 2: embedded */ - - /* Musicbrainz Track ID */ - char* mb_track_id; -}; - -enum { - ID3_VER_1_0 = 1, - ID3_VER_1_1, - ID3_VER_2_2, - ID3_VER_2_3, - ID3_VER_2_4 -}; - -bool get_mp3_metadata(int fd, struct mp3entry *entry, const char *filename); -bool mp3info(struct mp3entry *entry, const char *filename); -char* id3_get_num_genre(unsigned int genre_num); -int getid3v2len(int fd); -void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig); -void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig); - -#endif diff --git a/apps/menus/recording_menu.c b/apps/menus/recording_menu.c index 53cebfe..3b5a25e 100644 --- a/apps/menus/recording_menu.c +++ b/apps/menus/recording_menu.c @@ -55,7 +55,7 @@ #endif #include "splash.h" #if CONFIG_CODEC == SWCODEC -#include "id3.h" +#include "metadata.h" #include "dsp.h" #include "menus/eq_menu.h" #ifdef HAVE_RECORDING diff --git a/apps/metadata.c b/apps/metadata.c index 17c89f1..8df046a 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -24,23 +24,126 @@ #include #include -#include "system.h" #include "playback.h" #include "debug.h" #include "logf.h" #include "cuesheet.h" #include "metadata.h" +#include "metadata/metadata_parsers.h" + #if CONFIG_CODEC == SWCODEC /* For trailing tag stripping */ #include "buffering.h" #include "metadata/metadata_common.h" -#include "metadata/metadata_parsers.h" #endif /* CONFIG_CODEC == SWCODEC */ +const struct afmt_entry audio_formats[AFMT_NUM_CODECS] = +{ + /* Unknown file format */ + [AFMT_UNKNOWN] = + AFMT_ENTRY("???", NULL, NULL, NULL ), + + /* MPEG Audio layer 1 */ + [AFMT_MPA_L1] = + AFMT_ENTRY("MP1", "mpa", NULL, "mp1\0" ), + /* MPEG Audio layer 2 */ + [AFMT_MPA_L2] = + AFMT_ENTRY("MP2", "mpa", NULL, "mpa\0mp2\0" ), + /* MPEG Audio layer 3 */ + [AFMT_MPA_L3] = + AFMT_ENTRY("MP3", "mpa", "mp3_enc", "mp3\0" ), + +#if CONFIG_CODEC == SWCODEC + /* Audio Interchange File Format */ + [AFMT_AIFF] = + AFMT_ENTRY("AIFF", "aiff", "aiff_enc", "aiff\0aif\0"), + /* Uncompressed PCM in a WAV file */ + [AFMT_PCM_WAV] = + AFMT_ENTRY("WAV", "wav", "wav_enc", "wav\0" ), + /* Ogg Vorbis */ + [AFMT_OGG_VORBIS] = + AFMT_ENTRY("Ogg", "vorbis", NULL, "ogg\0" ), + /* FLAC */ + [AFMT_FLAC] = + AFMT_ENTRY("FLAC", "flac", NULL, "flac\0" ), + /* Musepack */ + [AFMT_MPC] = + AFMT_ENTRY("MPC", "mpc", NULL, "mpc\0" ), + /* A/52 (aka AC3) audio */ + [AFMT_A52] = + AFMT_ENTRY("AC3", "a52", NULL, "a52\0ac3\0" ), + /* WavPack */ + [AFMT_WAVPACK] = + AFMT_ENTRY("WV", "wavpack", "wavpack_enc", "wv\0" ), + /* Apple Lossless Audio Codec */ + [AFMT_ALAC] = + AFMT_ENTRY("ALAC", "alac", NULL, "m4a\0m4b\0" ), + /* Advanced Audio Coding in M4A container */ + [AFMT_AAC] = + AFMT_ENTRY("AAC", "aac", NULL, "mp4\0" ), + /* Shorten */ + [AFMT_SHN] = + AFMT_ENTRY("SHN", "shorten", NULL, "shn\0" ), + /* SID File Format */ + [AFMT_SID] = + AFMT_ENTRY("SID", "sid", NULL, "sid\0" ), + /* ADX File Format */ + [AFMT_ADX] = + AFMT_ENTRY("ADX", "adx", NULL, "adx\0" ), + /* NESM (NES Sound Format) */ + [AFMT_NSF] = + AFMT_ENTRY("NSF", "nsf", NULL, "nsf\0nsfe\0" ), + /* Speex File Format */ + [AFMT_SPEEX] = + AFMT_ENTRY("Speex","speex", NULL, "spx\0" ), + /* SPC700 Save State */ + [AFMT_SPC] = + AFMT_ENTRY("SPC", "spc", NULL, "spc\0" ), + /* APE (Monkey's Audio) */ + [AFMT_APE] = + AFMT_ENTRY("APE", "ape", NULL, "ape\0mac\0" ), + /* WMA (WMAV1/V2 in ASF) */ + [AFMT_WMA] = + AFMT_ENTRY("WMA", "wma", NULL, "wma\0wmv\0asf\0" ), + /* Amiga MOD File */ + [AFMT_MOD] = + AFMT_ENTRY("MOD", "mod", NULL, "mod\0" ), + /* Amiga SAP File */ + [AFMT_SAP] = + AFMT_ENTRY("SAP", "asap", NULL, "sap\0" ), +#endif +}; + +#if CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING) +/* get REC_FORMAT_* corresponding AFMT_* */ +const int rec_format_afmt[REC_NUM_FORMATS] = +{ + /* give AFMT_UNKNOWN by default */ + [0 ... REC_NUM_FORMATS-1] = AFMT_UNKNOWN, + /* add new entries below this line */ + [REC_FORMAT_AIFF] = AFMT_AIFF, + [REC_FORMAT_MPA_L3] = AFMT_MPA_L3, + [REC_FORMAT_WAVPACK] = AFMT_WAVPACK, + [REC_FORMAT_PCM_WAV] = AFMT_PCM_WAV, +}; + +/* get AFMT_* corresponding REC_FORMAT_* */ +const int afmt_rec_format[AFMT_NUM_CODECS] = +{ + /* give -1 by default */ + [0 ... AFMT_NUM_CODECS-1] = -1, + /* add new entries below this line */ + [AFMT_AIFF] = REC_FORMAT_AIFF, + [AFMT_MPA_L3] = REC_FORMAT_MPA_L3, + [AFMT_WAVPACK] = REC_FORMAT_WAVPACK, + [AFMT_PCM_WAV] = REC_FORMAT_PCM_WAV, +}; +#endif /* CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING) */ + /* Simple file type probing by looking at the filename extension. */ unsigned int probe_file_format(const char *filename) @@ -78,6 +181,23 @@ unsigned int probe_file_format(const char *filename) return AFMT_UNKNOWN; } +/* Note, that this returns false for successful, true for error! */ +bool mp3info(struct mp3entry *entry, const char *filename) +{ + int fd; + bool result; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return true; + + result = !get_metadata(entry, fd, filename); + + close(fd); + + return result; +} + /* Get metadata for track - return false if parsing showed problems with the * file that would prevent playback. */ @@ -314,3 +434,50 @@ void strip_tags(int handle_id) bufcuttail(handle_id, len); } #endif /* CONFIG_CODEC == SWCODEC */ + +void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig) +{ + long offset; + if (orig > dest) + offset = - ((size_t)orig - (size_t)dest); + else + offset = (size_t)dest - (size_t)orig; + + if (entry->title) + entry->title += offset; + if (entry->artist) + entry->artist += offset; + if (entry->album) + entry->album += offset; + if (entry->genre_string && !id3_is_genre_string(entry->genre_string)) + /* Don't adjust that if it points to an entry of the "genres" array */ + entry->genre_string += offset; + if (entry->track_string) + entry->track_string += offset; + if (entry->disc_string) + entry->disc_string += offset; + if (entry->year_string) + entry->year_string += offset; + if (entry->composer) + entry->composer += offset; + if (entry->comment) + entry->comment += offset; + if (entry->albumartist) + entry->albumartist += offset; + if (entry->grouping) + entry->grouping += offset; +#if CONFIG_CODEC == SWCODEC + if (entry->track_gain_string) + entry->track_gain_string += offset; + if (entry->album_gain_string) + entry->album_gain_string += offset; +#endif + if (entry->mb_track_id) + entry->mb_track_id += offset; +} + +void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig) +{ + memcpy(dest, orig, sizeof(struct mp3entry)); + adjust_mp3entry(dest, dest, orig); +} diff --git a/apps/metadata.h b/apps/metadata.h index c496f40..b190986 100644 --- a/apps/metadata.h +++ b/apps/metadata.h @@ -23,11 +23,228 @@ #define _METADATA_H #include +#include "file.h" #include "config.h" -#include "id3.h" + + +/* Audio file types. */ +/* NOTE: The values of the AFMT_* items are used for the %fc tag in the WPS + - so new entries MUST be added to the end to maintain compatibility. + */ +enum +{ + AFMT_UNKNOWN = 0, /* Unknown file format */ + + /* start formats */ + + AFMT_MPA_L1, /* MPEG Audio layer 1 */ + AFMT_MPA_L2, /* MPEG Audio layer 2 */ + AFMT_MPA_L3, /* MPEG Audio layer 3 */ + +#if CONFIG_CODEC == SWCODEC + AFMT_AIFF, /* Audio Interchange File Format */ + AFMT_PCM_WAV, /* Uncompressed PCM in a WAV file */ + AFMT_OGG_VORBIS, /* Ogg Vorbis */ + AFMT_FLAC, /* FLAC */ + AFMT_MPC, /* Musepack */ + AFMT_A52, /* A/52 (aka AC3) audio */ + AFMT_WAVPACK, /* WavPack */ + AFMT_ALAC, /* Apple Lossless Audio Codec */ + AFMT_AAC, /* Advanced Audio Coding (AAC) in M4A container */ + AFMT_SHN, /* Shorten */ + AFMT_SID, /* SID File Format */ + AFMT_ADX, /* ADX File Format */ + AFMT_NSF, /* NESM (NES Sound Format) */ + AFMT_SPEEX, /* Ogg Speex speech */ + AFMT_SPC, /* SPC700 save state */ + AFMT_APE, /* Monkey's Audio (APE) */ + AFMT_WMA, /* WMAV1/V2 in ASF */ + AFMT_MOD, /* Amiga MOD File Format */ + AFMT_SAP, /* Amiga 8Bit SAP Format */ +#endif + + /* add new formats at any index above this line to have a sensible order - + specified array index inits are used */ + /* format arrays defined in id3.c */ + + AFMT_NUM_CODECS, + +#if CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) + /* masks to decompose parts */ + CODEC_AFMT_MASK = 0x0fff, + CODEC_TYPE_MASK = 0x7000, + + /* switch for specifying codec type when requesting a filename */ + CODEC_TYPE_DECODER = (0 << 12), /* default */ + CODEC_TYPE_ENCODER = (1 << 12), +#endif /* CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) */ +}; + +#if CONFIG_CODEC == SWCODEC +#define CODEC_EXTENSION "codec" + +#ifdef HAVE_RECORDING +#define ENCODER_SUFFIX "_enc" +enum rec_format_indexes +{ + __REC_FORMAT_START_INDEX = -1, + + /* start formats */ + + REC_FORMAT_PCM_WAV, + REC_FORMAT_AIFF, + REC_FORMAT_WAVPACK, + REC_FORMAT_MPA_L3, + + /* add new formats at any index above this line to have a sensible order - + specified array index inits are used + REC_FORMAT_CFG_NUM_BITS should allocate enough bits to hold the range + REC_FORMAT_CFG_VALUE_LIST should be in same order as indexes + */ + + REC_NUM_FORMATS, + + REC_FORMAT_DEFAULT = REC_FORMAT_PCM_WAV, + REC_FORMAT_CFG_NUM_BITS = 2 +}; + +#define REC_FORMAT_CFG_VAL_LIST "wave,aiff,wvpk,mpa3" + +/* get REC_FORMAT_* corresponding AFMT_* */ +extern const int rec_format_afmt[REC_NUM_FORMATS]; +/* get AFMT_* corresponding REC_FORMAT_* */ +extern const int afmt_rec_format[AFMT_NUM_CODECS]; + +#define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \ + { label, root_fname, enc_root_fname, ext_list } +#else /* !HAVE_RECORDING */ +#define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \ + { label, root_fname, ext_list } +#endif /* HAVE_RECORDING */ + +#else /* !SWCODEC */ + +#define AFMT_ENTRY(label, root_fname, enc_root_fname, ext_list) \ + { label, ext_list } +#endif /* CONFIG_CODEC == SWCODEC */ + +/** Database of audio formats **/ +/* record describing the audio format */ +struct afmt_entry +{ + char label[8]; /* format label */ +#if CONFIG_CODEC == SWCODEC + char *codec_root_fn; /* root codec filename (sans _enc and .codec) */ +#ifdef HAVE_RECORDING + char *codec_enc_root_fn; /* filename of encoder codec */ +#endif +#endif + char *ext_list; /* double NULL terminated extension + list for type with the first as + the default for recording */ +}; + +/* database of labels and codecs. add formats per above enum */ +extern const struct afmt_entry audio_formats[AFMT_NUM_CODECS]; + +#define ID3V2_BUF_SIZE 300 + +enum { + ID3_VER_1_0 = 1, + ID3_VER_1_1, + ID3_VER_2_2, + ID3_VER_2_3, + ID3_VER_2_4 +}; + +struct mp3entry { + char path[MAX_PATH]; + char* title; + char* artist; + char* album; + char* genre_string; + char* disc_string; + char* track_string; + char* year_string; + char* composer; + char* comment; + char* albumartist; + char* grouping; + int discnum; + int tracknum; + int version; + int layer; + int year; + unsigned char id3version; + unsigned int codectype; + unsigned int bitrate; + 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 */ + + /* Added for Vorbis */ + unsigned long samples; /* number of samples in track */ + + /* MP3 stream specific info */ + unsigned long frame_count; /* number of frames in the file (if VBR) */ + + /* Used for A52/AC3 */ + unsigned long bytesperframe; /* number of bytes per frame (if CBR) */ + + /* 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 */ + + /* these following two fields are used for local buffering */ + char id3v2buf[ID3V2_BUF_SIZE]; + char id3v1buf[4][92]; + + /* resume related */ + unsigned long offset; /* bytes played */ + int index; /* playlist index */ + + /* runtime database fields */ + long tagcache_idx; /* 0=invalid, otherwise idx+1 */ + int rating; + int score; + long playcount; + long lastplayed; + long playtime; + + /* replaygain support */ + +#if CONFIG_CODEC == SWCODEC + char* track_gain_string; + char* album_gain_string; + long track_gain; /* 7.24 signed fixed point. 0 for no gain. */ + long album_gain; + long track_peak; /* 7.24 signed fixed point. 0 for no peak. */ + long album_peak; +#endif + + /* Cuesheet support */ + int cuesheet_type; /* 0: none, 1: external, 2: embedded */ + + /* Musicbrainz Track ID */ + char* mb_track_id; +}; unsigned int probe_file_format(const char *filename); bool get_metadata(struct mp3entry* id3, int fd, const char* trackname); +bool mp3info(struct mp3entry *entry, const char *filename); +void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig); +void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig); #if CONFIG_CODEC == SWCODEC void strip_tags(int handle_id); #endif diff --git a/apps/metadata/a52.c b/apps/metadata/a52.c index bcfd3c7..c35b32d 100644 --- a/apps/metadata/a52.c +++ b/apps/metadata/a52.c @@ -19,7 +19,7 @@ * ****************************************************************************/ -#include "id3.h" +#include "metadata.h" #include "logf.h" #include "metadata_parsers.h" diff --git a/apps/metadata/adx.c b/apps/metadata/adx.c index c5da0de..a903a6d 100644 --- a/apps/metadata/adx.c +++ b/apps/metadata/adx.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "debug.h" diff --git a/apps/metadata/aiff.c b/apps/metadata/aiff.c index 74e2465..cb18e92 100644 --- a/apps/metadata/aiff.c +++ b/apps/metadata/aiff.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" diff --git a/apps/metadata/ape.c b/apps/metadata/ape.c index 7e9100a..dcb3597 100644 --- a/apps/metadata/ape.c +++ b/apps/metadata/ape.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "structec.h" diff --git a/apps/metadata/asap.c b/apps/metadata/asap.c index 9bd615a..128a18d 100644 --- a/apps/metadata/asap.c +++ b/apps/metadata/asap.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "rbunicode.h" diff --git a/apps/metadata/asf.c b/apps/metadata/asf.c index 255a0bc..611cc2a 100644 --- a/apps/metadata/asf.c +++ b/apps/metadata/asf.c @@ -25,7 +25,7 @@ #include #include -#include "id3.h" +#include "metadata.h" #include "replaygain.h" #include "debug.h" #include "rbunicode.h" diff --git a/apps/metadata/flac.c b/apps/metadata/flac.c index 286d356..a50649e 100644 --- a/apps/metadata/flac.c +++ b/apps/metadata/flac.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "logf.h" diff --git a/apps/metadata/metadata_common.c b/apps/metadata/metadata_common.c index e4df874..94ff212 100644 --- a/apps/metadata/metadata_common.c +++ b/apps/metadata/metadata_common.c @@ -25,8 +25,9 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" +#include "metadata_parsers.h" #include "replaygain.h" /* Skip an ID3v2 tag if it can be found. We assume the tag is located at the diff --git a/apps/metadata/metadata_common.h b/apps/metadata/metadata_common.h index 3d9a075..f57690a 100644 --- a/apps/metadata/metadata_common.h +++ b/apps/metadata/metadata_common.h @@ -18,7 +18,7 @@ * KIND, either express or implied. * ****************************************************************************/ -#include "id3.h" +#include "metadata.h" #ifdef ROCKBOX_BIG_ENDIAN #define IS_BIG_ENDIAN 1 diff --git a/apps/metadata/metadata_parsers.h b/apps/metadata/metadata_parsers.h index 00ad112..1521f13 100644 --- a/apps/metadata/metadata_parsers.h +++ b/apps/metadata/metadata_parsers.h @@ -18,7 +18,11 @@ * KIND, either express or implied. * ****************************************************************************/ -#include "id3.h" + +char* id3_get_num_genre(unsigned int genre_num); +bool id3_is_genre_string(const char *string); +int getid3v2len(int fd); +bool get_mp3_metadata(int fd, struct mp3entry* id3, const char *filename); bool get_adx_metadata(int fd, struct mp3entry* id3); bool get_aiff_metadata(int fd, struct mp3entry* id3); diff --git a/apps/metadata/mod.c b/apps/metadata/mod.c index 38adeea..e100904 100644 --- a/apps/metadata/mod.c +++ b/apps/metadata/mod.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "rbunicode.h" diff --git a/apps/metadata/monkeys.c b/apps/metadata/monkeys.c index d59e7ee..1cacff1 100644 --- a/apps/metadata/monkeys.c +++ b/apps/metadata/monkeys.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" diff --git a/apps/metadata/mp3.c b/apps/metadata/mp3.c new file mode 100644 index 0000000..8c85c89 --- /dev/null +++ b/apps/metadata/mp3.c @@ -0,0 +1,1183 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Daniel Stenberg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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�deman. It has since been extended and enhanced pretty much by + * all sorts of friendly Rockbox people. + * + */ + + /* tagResolver and associated code copyright 2003 Thomas Paul Diffenbach + */ + +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "file.h" +#include "logf.h" + +#include "mp3data.h" +#include "system.h" +#include "replaygain.h" +#include "rbunicode.h" + +static unsigned long unsync(unsigned long b0, + unsigned long b1, + unsigned long b2, + unsigned long b3) +{ + return (((long)(b0 & 0x7F) << (3*7)) | + ((long)(b1 & 0x7F) << (2*7)) | + ((long)(b2 & 0x7F) << (1*7)) | + ((long)(b3 & 0x7F) << (0*7))); +} + +static const char* const genres[] = { + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", + "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", + "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", + "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", + "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", + "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", + "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", + "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", + "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", + "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", + "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", + "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", + "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", + + /* winamp extensions */ + "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", + "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", + "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", + "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", + "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", + "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", + "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", + "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", + "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", + "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap", + "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian", + "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "Jpop", + "Synthpop" +}; + +char* id3_get_num_genre(unsigned int genre_num) +{ + if (genre_num < sizeof(genres)/sizeof(char*)) + return (char*)genres[genre_num]; + return NULL; +} + +/* True if the string is from the "genres" array */ +bool id3_is_genre_string(const char *string) +{ + return ( string >= genres[0] && + string <= genres[sizeof(genres)/sizeof(char*) - 1] ); +} + +/* + HOW TO ADD ADDITIONAL ID3 VERSION 2 TAGS + Code and comments by Thomas Paul Diffenbach + + To add another ID3v2 Tag, do the following: + 1. add a char* named for the tag to struct mp3entry in id3.h, + (I (tpd) prefer to use char* rather than ints, even for what seems like + numerical values, for cases where a number won't do, e.g., + YEAR: "circa 1765", "1790/1977" (composed/performed), "28 Feb 1969" + TRACK: "1/12", "1 of 12", GENRE: "Freeform genre name" + Text is more flexible, and as the main use of id3 data is to + display it, converting it to an int just means reconverting to + display it, at a runtime cost.) + + 2. If any special processing beyond copying the tag value from the Id3 + block to the struct mp3entry is rrequired (such as converting to an + int), write a function to perform this special processing. + + This function's prototype must match that of + typedef tagPostProcessFunc, that is it must be: + int func( struct mp3entry*, char* tag, int bufferpos ) + the first argument is a pointer to the current mp3entry structure the + second argument is a pointer to the null terminated string value of the + tag found the third argument is the offset of the next free byte in the + mp3entry's buffer your function should return the corrected offset; if + you don't lengthen or shorten the tag string, you can return the third + argument unchanged. + + Unless you have a good reason no to, make the function static. + TO JUST COPY THE TAG NO SPECIAL PROCESSING FUNCTION IS NEEDED. + + 3. add one or more entries to the tagList array, using the format: + char* ID3 Tag symbolic name -- see the ID3 specification for these, + sizeof() that name minus 1, + offsetof( struct mp3entry, variable_name_in_struct_mp3entry ), + pointer to your special processing function or NULL + if you need no special processing + flag indicating if this tag is binary or textual + Many ID3 symbolic names come in more than one form. You can add both + forms, each referencing the same variable in struct mp3entry. + If both forms are present, the last found will be used. + Note that the offset can be zero, in which case no entry will be set + in the mp3entry struct; the frame is still read into the buffer and + the special processing function is called (several times, if there + are several frames with the same name). + + 4. Alternately, use the TAG_LIST_ENTRY macro with + ID3 tag symbolic name, + variable in struct mp3entry, + special processing function address + + 5. Add code to wps-display.c function get_tag to assign a printf-like + format specifier for the tag */ + +/* Structure for ID3 Tag extraction information */ +struct tag_resolver { + const char* tag; + int tag_length; + size_t offset; + int (*ppFunc)(struct mp3entry*, char* tag, int bufferpos); + bool binary; +}; + +static bool global_ff_found; + +static int unsynchronize(char* tag, int len, bool *ff_found) +{ + int i; + unsigned char c; + unsigned char *rp, *wp; + + wp = rp = (unsigned char *)tag; + + rp = (unsigned char *)tag; + for(i = 0;i < len;i++) { + /* Read the next byte and write it back, but don't increment the + write pointer */ + c = *rp++; + *wp = c; + if(*ff_found) { + /* Increment the write pointer if it isn't an unsynch pattern */ + if(c != 0) + wp++; + *ff_found = false; + } else { + if(c == 0xff) + *ff_found = true; + wp++; + } + } + return (long)wp - (long)tag; +} + +static int unsynchronize_frame(char* tag, int len) +{ + bool ff_found = false; + + return unsynchronize(tag, len, &ff_found); +} + +static int read_unsynched(int fd, void *buf, int len) +{ + int i; + int rc; + int remaining = len; + char *wp; + char *rp; + + wp = buf; + + while(remaining) { + rp = wp; + rc = read(fd, rp, remaining); + if(rc <= 0) + return rc; + + i = unsynchronize(wp, remaining, &global_ff_found); + remaining -= i; + wp += i; + } + + return len; +} + +static int skip_unsynched(int fd, int len) +{ + int rc; + int remaining = len; + int rlen; + char buf[32]; + + while(remaining) { + rlen = MIN(sizeof(buf), (unsigned int)remaining); + rc = read(fd, buf, rlen); + if(rc <= 0) + return rc; + + remaining -= unsynchronize(buf, rlen, &global_ff_found); + } + + return len; +} + +/* parse numeric value from string */ +static int parsetracknum( struct mp3entry* entry, char* tag, int bufferpos ) +{ + entry->tracknum = atoi( tag ); + return bufferpos; +} + +/* parse numeric value from string */ +static int parsediscnum( struct mp3entry* entry, char* tag, int bufferpos ) +{ + entry->discnum = atoi( tag ); + return bufferpos; +} + +/* parse numeric value from string */ +static int parseyearnum( struct mp3entry* entry, char* tag, int bufferpos ) +{ + entry->year = atoi( tag ); + return bufferpos; +} + +/* parse numeric genre from string, version 2.2 and 2.3 */ +static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos ) +{ + if(entry->id3version >= ID3_VER_2_4) { + /* In version 2.4 and up, there are no parentheses, and the genre frame + is a list of strings, either numbers or text. */ + + /* Is it a number? */ + if(isdigit(tag[0])) { + entry->genre_string = id3_get_num_genre(atoi( tag )); + return tag - entry->id3v2buf; + } else { + entry->genre_string = tag; + return bufferpos; + } + } else { + if( tag[0] == '(' && tag[1] != '(' ) { + entry->genre_string = id3_get_num_genre(atoi( tag + 1 )); + return tag - entry->id3v2buf; + } + else { + entry->genre_string = tag; + return bufferpos; + } + } +} + +#if CONFIG_CODEC == SWCODEC +/* parse user defined text, looking for replaygain information. */ +static int parseuser( struct mp3entry* entry, char* tag, int bufferpos ) +{ + char* value = NULL; + int desc_len = strlen(tag); + int value_len = 0; + + if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) { + /* At least part of the value was read, so we can safely try to + * parse it + */ + value = tag + desc_len + 1; + value_len = parse_replaygain(tag, value, entry, tag, + bufferpos - (tag - entry->id3v2buf)); + } + + return tag - entry->id3v2buf + value_len; +} + +/* parse RVA2 binary data and convert to replaygain information. */ +static int parserva2( struct mp3entry* entry, char* tag, int bufferpos ) +{ + int desc_len = strlen(tag); + int start_pos = tag - entry->id3v2buf; + int end_pos = start_pos + desc_len + 5; + int value_len = 0; + unsigned char* value = tag + desc_len + 1; + + /* Only parse RVA2 replaygain tags if tag version == 2.4 and channel + * type is master volume. + */ + if (entry->id3version == ID3_VER_2_4 && end_pos < bufferpos + && *value++ == 1) { + long gain = 0; + long peak = 0; + long peakbits; + long peakbytes; + bool album = false; + + /* The RVA2 specification is unclear on some things (id string and + * peak volume), but this matches how Quod Libet use them. + */ + + gain = (int16_t) ((value[0] << 8) | value[1]); + value += 2; + peakbits = *value++; + peakbytes = (peakbits + 7) / 8; + + /* Only use the topmost 24 bits for peak volume */ + if (peakbytes > 3) { + peakbytes = 3; + } + + /* Make sure the peak bits were read */ + if (end_pos + peakbytes < bufferpos) { + long shift = ((8 - (peakbits & 7)) & 7) + (3 - peakbytes) * 8; + + for ( ; peakbytes; peakbytes--) { + peak <<= 8; + peak += *value++; + } + + peak <<= shift; + + if (peakbits > 24) { + peak += *value >> (8 - shift); + } + } + + if (strcasecmp(tag, "album") == 0) { + album = true; + } else if (strcasecmp(tag, "track") != 0) { + /* Only accept non-track values if we don't have any previous + * value. + */ + if (entry->track_gain != 0) { + return start_pos; + } + } + + value_len = parse_replaygain_int(album, gain, peak * 2, entry, + tag, sizeof(entry->id3v2buf) - start_pos); + } + + return start_pos + value_len; +} +#endif + +static int parsembtid( struct mp3entry* entry, char* tag, int bufferpos ) +{ + char* value = NULL; + int desc_len = strlen(tag); + /*DEBUGF("MBID len: %d\n", desc_len);*/ + int value_len = 0; + + if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) + { + value = tag + desc_len + 1; + + if (strcasecmp(tag, "http://musicbrainz.org") == 0) + { + /* Musicbrainz track IDs are always 36 chars long plus null */ + value_len = 37; + + entry->mb_track_id = value; + + /*DEBUGF("ENTRY: %s LEN: %d\n", entry->mb_track_id, strlen(entry->mb_track_id));*/ + } + } + + return tag - entry->id3v2buf + value_len; +} + +static const struct tag_resolver taglist[] = { + { "TPE1", 4, offsetof(struct mp3entry, artist), NULL, false }, + { "TP1", 3, offsetof(struct mp3entry, artist), NULL, false }, + { "TIT2", 4, offsetof(struct mp3entry, title), NULL, false }, + { "TT2", 3, offsetof(struct mp3entry, title), NULL, false }, + { "TALB", 4, offsetof(struct mp3entry, album), NULL, false }, + { "TAL", 3, offsetof(struct mp3entry, album), NULL, false }, + { "TRK", 3, offsetof(struct mp3entry, track_string), &parsetracknum, false }, + { "TPOS", 4, offsetof(struct mp3entry, disc_string), &parsediscnum, false }, + { "TRCK", 4, offsetof(struct mp3entry, track_string), &parsetracknum, false }, + { "TDRC", 4, offsetof(struct mp3entry, year_string), &parseyearnum, false }, + { "TYER", 4, offsetof(struct mp3entry, year_string), &parseyearnum, false }, + { "TYE", 3, offsetof(struct mp3entry, year_string), &parseyearnum, false }, + { "TCOM", 4, offsetof(struct mp3entry, composer), NULL, false }, + { "TPE2", 4, offsetof(struct mp3entry, albumartist), NULL, false }, + { "TP2", 3, offsetof(struct mp3entry, albumartist), NULL, false }, + { "TIT1", 4, offsetof(struct mp3entry, grouping), NULL, false }, + { "TT1", 3, offsetof(struct mp3entry, grouping), NULL, false }, + { "COMM", 4, offsetof(struct mp3entry, comment), NULL, false }, + { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre, false }, + { "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre, false }, +#if CONFIG_CODEC == SWCODEC + { "TXXX", 4, 0, &parseuser, false }, + { "RVA2", 4, 0, &parserva2, true }, +#endif + { "UFID", 4, 0, &parsembtid, false }, +}; + +#define TAGLIST_SIZE ((int)(sizeof(taglist) / sizeof(taglist[0]))) + +/* Get the length of an ID3 string in the given encoding. Returns the length + * in bytes, including end nil, or -1 if the encoding is unknown. + */ +static int unicode_len(char encoding, const void* string) +{ + int len = 0; + + if (encoding == 0x01 || encoding == 0x02) { + char first; + const char *s = string; + /* string might be unaligned, so using short* can crash on ARM and SH1 */ + do { + first = *s++; + } while ((first | *s++) != 0); + + len = s - (const char*) string; + } else { + len = strlen((char*) string) + 1; + } + + return len; +} + +/* Checks to see if the passed in string is a 16-bit wide Unicode v2 + string. If it is, we convert it to a UTF-8 string. If it's not unicode, + we convert from the default codepage */ +static int unicode_munge(char* string, char* utf8buf, int *len) { + long tmp; + bool le = false; + int i = 0; + unsigned char *str = (unsigned char *)string; + int templen = 0; + unsigned char* utf8 = (unsigned char *)utf8buf; + + switch (str[0]) { + case 0x00: /* Type 0x00 is ordinary ISO 8859-1 */ + str++; + (*len)--; + utf8 = iso_decode(str, utf8, -1, *len); + *utf8 = 0; + *len = (unsigned long)utf8 - (unsigned long)utf8buf; + break; + + case 0x01: /* Unicode with or without BOM */ + case 0x02: + (*len)--; + str++; + + /* Handle frames with more than one string + (needed for TXXX frames).*/ + do { + tmp = bytes2int(0, 0, str[0], str[1]); + + /* Now check if there is a BOM + (zero-width non-breaking space, 0xfeff) + and if it is in little or big endian format */ + if(tmp == 0xfffe) { /* Little endian? */ + le = true; + str += 2; + (*len)-=2; + } else if(tmp == 0xfeff) { /* Big endian? */ + str += 2; + (*len)-=2; + } else + /* If there is no BOM (which is a specification violation), + let's try to guess it. If one of the bytes is 0x00, it is + probably the most significant one. */ + if(str[1] == 0) + le = true; + + do { + if(le) + utf8 = utf16LEdecode(str, utf8, 1); + else + utf8 = utf16BEdecode(str, utf8, 1); + + str+=2; + i += 2; + } while((str[0] || str[1]) && (i < *len)); + + *utf8++ = 0; /* Terminate the string */ + templen += (strlen(&utf8buf[templen]) + 1); + str += 2; + i+=2; + } while(i < *len); + *len = templen - 1; + break; + + case 0x03: /* UTF-8 encoded string */ + for(i=0; i < *len; i++) + utf8[i] = str[i+1]; + (*len)--; + break; + + default: /* Plain old string */ + utf8 = iso_decode(str, utf8, -1, *len); + *utf8 = 0; + *len = (unsigned long)utf8 - (unsigned long)utf8buf; + break; + } + return 0; +} + +/* + * Sets the title of an MP3 entry based on its ID3v1 tag. + * + * Arguments: file - the MP3 file to scen for a ID3v1 tag + * entry - the entry to set the title in + * + * Returns: true if a title was found and created, else false + */ +static bool setid3v1title(int fd, struct mp3entry *entry) +{ + unsigned char buffer[128]; + static const char offsets[] = {3, 33, 63, 97, 93, 125, 127}; + int i, j; + unsigned char* utf8; + + if (-1 == lseek(fd, -128, SEEK_END)) + return false; + + if (read(fd, buffer, sizeof buffer) != sizeof buffer) + return false; + + if (strncmp((char *)buffer, "TAG", 3)) + return false; + + entry->id3v1len = 128; + entry->id3version = ID3_VER_1_0; + + for (i=0; i < (int)sizeof offsets; i++) { + unsigned char* ptr = (unsigned char *)buffer + offsets[i]; + + switch(i) { + case 0: + case 1: + case 2: + /* kill trailing space in strings */ + for (j=29; j && (ptr[j]==0 || ptr[j]==' '); j--) + ptr[j] = 0; + /* convert string to utf8 */ + utf8 = (unsigned char *)entry->id3v1buf[i]; + utf8 = iso_decode(ptr, utf8, -1, 30); + /* make sure string is terminated */ + *utf8 = 0; + break; + + case 3: + /* kill trailing space in strings */ + for (j=27; j && (ptr[j]==0 || ptr[j]==' '); j--) + ptr[j] = 0; + /* convert string to utf8 */ + utf8 = (unsigned char *)entry->id3v1buf[3]; + utf8 = iso_decode(ptr, utf8, -1, 28); + /* make sure string is terminated */ + *utf8 = 0; + break; + + case 4: + ptr[4] = 0; + entry->year = atoi((char *)ptr); + break; + + case 5: + /* id3v1.1 uses last two bytes of comment field for track + number: first must be 0 and second is track num */ + if (!ptr[0] && ptr[1]) { + entry->tracknum = ptr[1]; + entry->id3version = ID3_VER_1_1; + } + break; + + case 6: + /* genre */ + entry->genre_string = id3_get_num_genre(ptr[0]); + break; + } + } + + entry->title = entry->id3v1buf[0]; + entry->artist = entry->id3v1buf[1]; + entry->album = entry->id3v1buf[2]; + entry->comment = entry->id3v1buf[3]; + + return true; +} + + +/* + * Sets the title of an MP3 entry based on its ID3v2 tag. + * + * Arguments: file - the MP3 file to scan for a ID3v2 tag + * entry - the entry to set the title in + * + * Returns: true if a title was found and created, else false + */ +static void setid3v2title(int fd, struct mp3entry *entry) +{ + int minframesize; + int size; + long bufferpos = 0, totframelen, framelen; + char header[10]; + char tmp[4]; + unsigned char version; + char *buffer = entry->id3v2buf; + int bytesread = 0; + int buffersize = sizeof(entry->id3v2buf); + unsigned char global_flags; + int flags; + int skip; + bool global_unsynch = false; + bool unsynch = false; + int i, j; + int rc; + + global_ff_found = false; + + /* Bail out if the tag is shorter than 10 bytes */ + if(entry->id3v2len < 10) + return; + + /* Read the ID3 tag version from the header */ + lseek(fd, 0, SEEK_SET); + if(10 != read(fd, header, 10)) + return; + + /* Get the total ID3 tag size */ + size = entry->id3v2len - 10; + + version = header[3]; + switch ( version ) { + case 2: + version = ID3_VER_2_2; + minframesize = 8; + break; + + case 3: + version = ID3_VER_2_3; + minframesize = 12; + break; + + case 4: + version = ID3_VER_2_4; + minframesize = 12; + break; + + default: + /* unsupported id3 version */ + return; + } + entry->id3version = version; + entry->tracknum = entry->year = entry->discnum = 0; + entry->title = entry->artist = entry->album = NULL; /* FIXME incomplete */ + + global_flags = header[5]; + + /* Skip the extended header if it is present */ + if(global_flags & 0x40) { + if(version == ID3_VER_2_3) { + if(10 != read(fd, header, 10)) + return; + /* The 2.3 extended header size doesn't include the header size + field itself. Also, it is not unsynched. */ + framelen = + bytes2int(header[0], header[1], header[2], header[3]) + 4; + + /* Skip the rest of the header */ + lseek(fd, framelen - 10, SEEK_CUR); + } + + if(version >= ID3_VER_2_4) { + if(4 != read(fd, header, 4)) + return; + + /* The 2.4 extended header size does include the entire header, + so here we can just skip it. This header is unsynched. */ + framelen = unsync(header[0], header[1], + header[2], header[3]); + + lseek(fd, framelen - 4, SEEK_CUR); + } + } + + /* Is unsynchronization applied? */ + if(global_flags & 0x80) { + global_unsynch = true; + } + + /* + * We must have at least minframesize bytes left for the + * remaining frames to be interesting + */ + while (size >= minframesize && bufferpos < buffersize - 1) { + flags = 0; + + /* Read frame header and check length */ + if(version >= ID3_VER_2_3) { + if(global_unsynch && version <= ID3_VER_2_3) + rc = read_unsynched(fd, header, 10); + else + rc = read(fd, header, 10); + if(rc != 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], + header[6], header[7]); + } else { + /* version .3 files don't use synchsafe ints for + * size */ + framelen = bytes2int(header[4], header[5], + header[6], header[7]); + } + } else { + if(6 != read(fd, header, 6)) + return; + /* Adjust for the 6 bytes we read */ + size -= 6; + + framelen = bytes2int(0, header[3], header[4], header[5]); + } + + logf("framelen = %ld", framelen); + if(framelen == 0){ + if (header[0] == 0 && header[1] == 0 && header[2] == 0) + return; + else + continue; + } + + unsynch = false; + + if(flags) + { + skip = 0; + + if (version >= ID3_VER_2_4) { + if(flags & 0x0040) { /* Grouping identity */ + lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */ + framelen--; + } + } else { + if(flags & 0x0020) { /* Grouping identity */ + lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */ + framelen--; + } + } + + if(flags & 0x000c) /* Compression or encryption */ + { + /* Skip it */ + size -= framelen; + lseek(fd, framelen, SEEK_CUR); + continue; + } + + if(flags & 0x0002) /* Unsynchronization */ + unsynch = true; + + if (version >= ID3_VER_2_4) { + if(flags & 0x0001) { /* Data length indicator */ + if(4 != read(fd, tmp, 4)) + return; + + /* We don't need the data length */ + framelen -= 4; + } + } + } + + /* Keep track of the remaining frame size */ + totframelen = framelen; + + /* 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) + framelen = buffersize - bufferpos - 1; + + logf("id3v2 frame: %.4s", header); + + /* Check for certain frame headers + + 'size' is the amount of frame bytes remaining. We decrement it by + the amount of bytes we read. If we fail to read as many bytes as + we expect, we assume that we can't read from this file, and bail + out. + + For each frame. we will iterate over the list of supported tags, + and read the tag into entry's buffer. All tags will be kept as + strings, for cases where a number won't do, e.g., YEAR: "circa + 1765", "1790/1977" (composed/performed), "28 Feb 1969" TRACK: + "1/12", "1 of 12", GENRE: "Freeform genre name" Text is more + flexible, and as the main use of id3 data is to display it, + converting it to an int just means reconverting to display it, at a + runtime cost. + + For tags that the current code does convert to ints, a post + processing function will be called via a pointer to function. */ + + for (i=0; ioffset ? (char**) (((char*)entry) + tr->offset) + : NULL; + char* tag; + + /* Only ID3_VER_2_2 uses frames with three-character names. */ + if (((version == ID3_VER_2_2) && (tr->tag_length != 3)) + || ((version > ID3_VER_2_2) && (tr->tag_length != 4))) { + continue; + } + + /* Note that parser functions sometimes set *ptag to NULL, so + * the "!*ptag" check here doesn't always have the desired + * effect. Should the parser functions (parsegenre in + * particular) be updated to handle the case of being called + * multiple times, or should the "*ptag" check be removed? + */ + if( (!ptag || !*ptag) && !memcmp( header, tr->tag, tr->tag_length ) ) { + + /* found a tag matching one in tagList, and not yet filled */ + tag = buffer + bufferpos; + + if(global_unsynch && version <= ID3_VER_2_3) + bytesread = read_unsynched(fd, tag, framelen); + else + bytesread = read(fd, tag, framelen); + + if( bytesread != framelen ) + return; + + size -= bytesread; + + if(unsynch || (global_unsynch && version >= ID3_VER_2_4)) + bytesread = unsynchronize_frame(tag, bytesread); + + /* the COMM frame has a 3 char field to hold an ISO-639-1 + * language string and an optional short description; + * remove them so unicode_munge can work correctly + */ + + if(!memcmp( header, "COMM", 4 )) { + int offset; + /* ignore comments with iTunes 7 soundcheck/gapless data */ + if(!strncmp(tag+4, "iTun", 4)) + break; + offset = 3 + unicode_len(*tag, tag + 4); + if(bytesread > offset) { + bytesread -= offset; + memmove(tag + 1, tag + 1 + offset, bytesread - 1); + } + } + + /* Attempt to parse Unicode string only if the tag contents + aren't binary */ + if(!tr->binary) { + /* UTF-8 could potentially be 3 times larger */ + /* so we need to create a new buffer */ + char utf8buf[(3 * bytesread) + 1]; + + unicode_munge( tag, utf8buf, &bytesread ); + + if(bytesread >= buffersize - bufferpos) + bytesread = buffersize - bufferpos - 1; + + for (j = 0; j < bytesread; j++) + tag[j] = utf8buf[j]; + + /* remove trailing spaces */ + while ( bytesread > 0 && isspace(tag[bytesread-1])) + bytesread--; + } + + tag[bytesread] = 0; + bufferpos += bytesread + 1; + + if (ptag) + *ptag = tag; + + if( tr->ppFunc ) + bufferpos = tr->ppFunc(entry, tag, bufferpos); + + /* Seek to the next frame */ + if(framelen < totframelen) + lseek(fd, totframelen - framelen, SEEK_CUR); + break; + } + } + + if( i == TAGLIST_SIZE ) { + /* no tag in tagList was found, or it was a repeat. + skip it using the total size */ + + if(global_unsynch && version <= ID3_VER_2_3) { + size -= skip_unsynched(fd, totframelen); + } else { + size -= totframelen; + if( lseek(fd, totframelen, SEEK_CUR) == -1 ) + return; + } + } + } +} + +/* + * Calculates the size of the ID3v2 tag. + * + * Arguments: file - the file to search for a tag. + * + * Returns: the size of the tag or 0 if none was found + */ +int getid3v2len(int fd) +{ + char buf[6]; + int offset; + + /* Make sure file has a ID3 tag */ + if((-1 == lseek(fd, 0, SEEK_SET)) || + (read(fd, buf, 6) != 6) || + (strncmp(buf, "ID3", strlen("ID3")) != 0)) + offset = 0; + + /* Now check what the ID3v2 size field says */ + else + if(read(fd, buf, 4) != 4) + offset = 0; + else + offset = unsync(buf[0], buf[1], buf[2], buf[3]) + 10; + + logf("ID3V2 Length: 0x%x", offset); + return offset; +} + +/* + * Calculates the length (in milliseconds) of an MP3 file. + * + * Modified to only use integers. + * + * Arguments: file - the file to calculate the length upon + * entry - the entry to update with the length + * + * Returns: the song length in milliseconds, + * 0 means that it couldn't be calculated + */ +static int getsonglength(int fd, struct mp3entry *entry) +{ + unsigned long filetime = 0; + struct mp3info info; + long bytecount; + + /* Start searching after ID3v2 header */ + if(-1 == lseek(fd, entry->id3v2len, SEEK_SET)) + return 0; + + bytecount = get_mp3file_info(fd, &info); + + logf("Space between ID3V2 tag and first audio frame: 0x%lx bytes", + bytecount); + + if(bytecount < 0) + return -1; + + bytecount += entry->id3v2len; + + /* Validate byte count, in case the file has been edited without + * updating the header. + */ + if (info.byte_count) + { + const unsigned long expected = entry->filesize - entry->id3v1len + - entry->id3v2len; + const unsigned long diff = MAX(10240, info.byte_count / 20); + + if ((info.byte_count > expected + diff) + || (info.byte_count < expected - diff)) + { + logf("Note: info.byte_count differs from expected value by " + "%ld bytes", labs((long) (expected - info.byte_count))); + info.byte_count = 0; + info.frame_count = 0; + info.file_time = 0; + info.enc_padding = 0; + + /* Even if the bitrate was based on "known bad" values, it + * should still be better for VBR files than using the bitrate + * of the first audio frame. + */ + } + } + + entry->bitrate = info.bitrate; + entry->frequency = info.frequency; + entry->version = info.version; + entry->layer = info.layer; + switch(entry->layer) { +#if CONFIG_CODEC==SWCODEC + case 0: + entry->codectype=AFMT_MPA_L1; + break; +#endif + case 1: + entry->codectype=AFMT_MPA_L2; + break; + case 2: + entry->codectype=AFMT_MPA_L3; + break; + } + + /* 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) + { + /* Prevent a division by zero */ + if (info.bitrate < 8) + filetime = 0; + else + filetime = (entry->filesize - bytecount) / (info.bitrate / 8); + /* bitrate is in kbps so this delivers milliseconds. Doing bitrate / 8 + * instead of filesize * 8 is exact, because mpeg audio bitrates are + * always multiples of 8, and it avoids overflows. */ + } + + entry->frame_count = info.frame_count; + + entry->vbr = info.is_vbr; + entry->has_toc = info.has_toc; + +#if CONFIG_CODEC==SWCODEC + entry->lead_trim = info.enc_delay; + entry->tail_trim = info.enc_padding; +#endif + + memcpy(entry->toc, info.toc, sizeof(info.toc)); + + entry->vbr_header_pos = info.vbr_header_pos; + + /* Update the seek point for the first playable frame */ + entry->first_frame_offset = bytecount; + logf("First frame is at %lx", entry->first_frame_offset); + + return filetime; +} + +/* + * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc) + * about an MP3 file and updates it's entry accordingly. + * + Note, that this returns true for successful, false for error! */ +bool get_mp3_metadata(int fd, struct mp3entry *entry, const char *filename) +{ +#if CONFIG_CODEC != SWCODEC + memset(entry, 0, sizeof(struct mp3entry)); +#endif + + strncpy(entry->path, filename, sizeof(entry->path)); + + entry->title = NULL; + entry->filesize = filesize(fd); + entry->id3v2len = getid3v2len(fd); + entry->tracknum = 0; + entry->discnum = 0; + + if (entry->id3v2len) + setid3v2title(fd, entry); + int len = getsonglength(fd, entry); + if (len < 0) + return false; + entry->length = len; + + /* 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) { + setid3v1title(fd, entry); + } + + if(!entry->length || (entry->filesize < 8 )) + /* no song length or less than 8 bytes is hereby considered to be an + invalid mp3 and won't be played by us! */ + return false; + + return true; +} + +#ifdef DEBUG_STANDALONE + +char *secs2str(int ms) +{ + static char buffer[32]; + int secs = ms/1000; + ms %= 1000; + snprintf(buffer, sizeof(buffer), "%d:%02d.%d", secs/60, secs%60, ms/100); + return buffer; +} + +int main(int argc, char **argv) +{ + int i; + for(i=1; i", + mp3.artist?mp3.artist:"", + mp3.album?mp3.album:"", + mp3.genre_string?mp3.genre_string:"", + mp3.genre, + mp3.composer?mp3.composer:"", + mp3.year_string?mp3.year_string:"", + mp3.year, + mp3.track_string?mp3.track_string:"", + mp3.tracknum, + secs2str(mp3.length), + mp3.length/1000, + mp3.bitrate, + mp3.frequency); + } + + return 0; +} + +#endif diff --git a/apps/metadata/mp4.c b/apps/metadata/mp4.c index 493bc48..803f82f 100644 --- a/apps/metadata/mp4.c +++ b/apps/metadata/mp4.c @@ -26,7 +26,7 @@ #include "system.h" #include "errno.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "logf.h" diff --git a/apps/metadata/mpc.c b/apps/metadata/mpc.c index dd83515..5ab1241 100644 --- a/apps/metadata/mpc.c +++ b/apps/metadata/mpc.c @@ -22,7 +22,7 @@ #include #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "logf.h" diff --git a/apps/metadata/ogg.c b/apps/metadata/ogg.c index edb55f5..cd4c85f 100644 --- a/apps/metadata/ogg.c +++ b/apps/metadata/ogg.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "logf.h" diff --git a/apps/metadata/sid.c b/apps/metadata/sid.c index 8741ce6..bab7233 100644 --- a/apps/metadata/sid.c +++ b/apps/metadata/sid.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "rbunicode.h" diff --git a/apps/metadata/spc.c b/apps/metadata/spc.c index 094fcce..786c678 100644 --- a/apps/metadata/spc.c +++ b/apps/metadata/spc.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "debug.h" diff --git a/apps/metadata/vorbis.c b/apps/metadata/vorbis.c index 19b7915..cfaa715 100644 --- a/apps/metadata/vorbis.c +++ b/apps/metadata/vorbis.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "structec.h" diff --git a/apps/metadata/wave.c b/apps/metadata/wave.c index 229d615..cf676f8 100644 --- a/apps/metadata/wave.c +++ b/apps/metadata/wave.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" diff --git a/apps/metadata/wavpack.c b/apps/metadata/wavpack.c index c695203..a5a342b 100644 --- a/apps/metadata/wavpack.c +++ b/apps/metadata/wavpack.c @@ -25,7 +25,7 @@ #include #include "system.h" -#include "id3.h" +#include "metadata.h" #include "metadata_common.h" #include "metadata_parsers.h" #include "logf.h" diff --git a/apps/mp3data.c b/apps/mp3data.c new file mode 100644 index 0000000..80870cd --- /dev/null +++ b/apps/mp3data.c @@ -0,0 +1,782 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Daniel Stenberg + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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 +#include +#include +#include +#include +#include "debug.h" +#include "logf.h" +#include "mp3data.h" +#include "file.h" +#include "buffer.h" + +// #define DEBUG_VERBOSE + +#define SYNC_MASK (0x7ffL << 21) +#define VERSION_MASK (3L << 19) +#define LAYER_MASK (3L << 17) +#define PROTECTION_MASK (1L << 16) +#define BITRATE_MASK (0xfL << 12) +#define SAMPLERATE_MASK (3L << 10) +#define PADDING_MASK (1L << 9) +#define PRIVATE_MASK (1L << 8) +#define CHANNELMODE_MASK (3L << 6) +#define MODE_EXT_MASK (3L << 4) +#define COPYRIGHT_MASK (1L << 3) +#define ORIGINAL_MASK (1L << 2) +#define EMPHASIS_MASK 3L + +/* MPEG Version table, sorted by version index */ +static const signed char version_table[4] = { + MPEG_VERSION2_5, -1, MPEG_VERSION2, MPEG_VERSION1 +}; + +/* 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 */ +}; + +/* Bitrate pointer table, indexed by version and layer */ +static const short *bitrate_table[3][3] = +{ + {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 unsigned short freq_table[3][3] = +{ + {44100, 48000, 32000}, /* MPEG Version 1 */ + {22050, 24000, 16000}, /* MPEG version 2 */ + {11025, 12000, 8000}, /* MPEG version 2.5 */ +}; + +unsigned long bytes2int(unsigned long b0, + unsigned long b1, + unsigned long b2, + unsigned long b3) +{ + return (((long)(b0 & 0xFF) << (3*8)) | + ((long)(b1 & 0xFF) << (2*8)) | + ((long)(b2 & 0xFF) << (1*8)) | + ((long)(b3 & 0xFF) << (0*8))); +} + +/* 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) == (1L << 19)) /* bad version? */ + return false; + if (!(head & LAYER_MASK)) /* no layer? */ + return false; +#if CONFIG_CODEC != SWCODEC + /* The MAS can't decode layer 1, so treat layer 1 data as invalid */ + if ((head & LAYER_MASK) == LAYER_MASK) + return false; +#endif + 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; + + return true; +} + +static bool mp3headerinfo(struct mp3info *info, unsigned long header) +{ + int bitindex, freqindex; + + /* MPEG Audio Version */ + if ((header & VERSION_MASK) >> 19 >= sizeof(version_table)) + return false; + + info->version = version_table[(header & VERSION_MASK) >> 19]; + if (info->version < 0) + return false; + + /* Layer */ + info->layer = 3 - ((header & LAYER_MASK) >> 17); + if (info->layer == 3) + return false; + + info->protection = (header & PROTECTION_MASK) ? true : false; + + /* Bitrate */ + bitindex = (header & BITRATE_MASK) >> 12; + info->bitrate = bitrate_table[info->version][info->layer][bitindex]; + if(info->bitrate == 0) + return false; + + /* 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 */ + 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; + } + + /* Frametime fraction denominator */ + if (freqindex != 0) { /* 48/32/24/16/12/8 kHz */ + info->ft_den = 1; /* integer number of milliseconds */ + } + else { /* 44.1/22.05/11.025 kHz */ + if (info->layer == 0) /* layer 1 */ + info->ft_den = 147; + else /* layer 2+3 */ + info->ft_den = 49; + } + /* Frametime fraction numerator */ + info->ft_num = 1000 * info->ft_den * info->frame_samples / info->frequency; + + 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: %08lx, 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->ft_num, info->ft_den); +#endif + return true; +} + +static unsigned long __find_next_frame(int fd, long *offset, long max_offset, + unsigned long last_header, + int(*getfunc)(int fd, unsigned char *c)) +{ + unsigned long header=0; + unsigned char tmp; + int i; + + long 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(!getfunc(fd, &tmp)) + return 0; + header |= tmp; + pos++; + } + + do { + header <<= 8; + if(!getfunc(fd, &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; + +#if defined(DEBUG) + if(*offset) + DEBUGF("Warning: skipping %ld bytes of garbage\n", *offset); +#endif + + return header; +} + +static int fileread(int fd, unsigned char *c) +{ + return read(fd, c, 1); +} + +unsigned long find_next_frame(int fd, long *offset, long max_offset, unsigned long last_header) +{ + return __find_next_frame(fd, offset, max_offset, last_header, fileread); +} + +#ifndef __PCTOOL__ +static int fnf_read_index; +static int fnf_buf_len; + +static int buf_getbyte(int fd, unsigned char *c) +{ + if(fnf_read_index < fnf_buf_len) + { + *c = audiobuf[fnf_read_index++]; + return 1; + } + else + { + fnf_buf_len = read(fd, audiobuf, audiobufend - audiobuf); + if(fnf_buf_len < 0) + return -1; + + fnf_read_index = 0; + + if(fnf_buf_len > 0) + { + *c = audiobuf[fnf_read_index++]; + return 1; + } + else + return 0; + } + return 0; +} + +static int buf_seek(int fd, 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, audiobuf, audiobufend - audiobuf); + 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; +} + +static unsigned long buf_find_next_frame(int fd, long *offset, long max_offset, + unsigned long last_header) +{ + return __find_next_frame(fd, offset, max_offset, last_header, buf_getbyte); +} + +static int audiobuflen; +static int mem_pos; +static int mem_cnt; +static int mem_maxlen; + +static int mem_getbyte(int dummy, unsigned char *c) +{ + dummy = dummy; + + *c = audiobuf[mem_pos++]; + if(mem_pos >= audiobuflen) + mem_pos = 0; + + if(mem_cnt++ >= mem_maxlen) + return 0; + else + return 1; +} + +unsigned long mem_find_next_frame(int startpos, long *offset, long max_offset, + unsigned long last_header) +{ + audiobuflen = audiobufend - audiobuf; + mem_pos = startpos; + mem_cnt = 0; + mem_maxlen = max_offset; + + return __find_next_frame(0, offset, max_offset, last_header, mem_getbyte); +} +#endif + +int get_mp3file_info(int fd, struct mp3info *info) +{ + unsigned char frame[1800]; + unsigned char *vbrheader; + unsigned long header; + long bytecount; + int num_offsets; + int frames_per_entry; + int i; + long offset; + int j; + long 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 CONFIG_CODEC==SWCODEC + /* These two are needed for proper LAME gapless MP3 playback */ + info->enc_delay = -1; + info->enc_padding = -1; +#endif + if(!mp3headerinfo(info, header)) + return -2; + + /* OK, we have found a frame. Let's see if it has a Xing header */ + if (info->frame_size-4 >= (int)sizeof(frame)) + { +#if defined(DEBUG) + DEBUGF("Error: Invalid id3 header, frame_size: %d\n", info->frame_size); +#endif + return -8; + } + + 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 (!memcmp(vbrheader, "Xing", 4) + || !memcmp(vbrheader, "Info", 4)) + { + int i = 8; /* Where to start parsing info */ + + /* DEBUGF("Xing/Info header\n"); */ + + /* Remember where in the file the Xing header is */ + info->vbr_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; + + /* Is it a VBR file? */ + info->is_vbr = info->is_xing_vbr = !memcmp(vbrheader, "Xing", 4); + + 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]); + 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; + } + + 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]); + i += 4; + } + + if (info->file_time && info->byte_count) + { + if (info->byte_count <= (ULONG_MAX/8)) + info->bitrate = info->byte_count * 8 / info->file_time; + else + info->bitrate = info->byte_count / (info->file_time >> 3); + } + else + info->bitrate = 0; + + if (vbrheader[7] & VBR_TOC_FLAG) /* Is table-of-contents there? */ + { + info->has_toc = true; + memcpy( info->toc, vbrheader+i, 100 ); + i += 100; + } + if (vbrheader[7] & VBR_QUALITY_FLAG) + { + /* We don't care about this, but need to skip it */ + i += 4; + } +#if CONFIG_CODEC==SWCODEC + i += 21; + info->enc_delay = (vbrheader[i] << 4) | (vbrheader[i + 1] >> 4); + info->enc_padding = ((vbrheader[i + 1] & 0x0f) << 8) | vbrheader[i + 2]; + /* TODO: This sanity checking is rather silly, seeing as how the LAME + header contains a CRC field that can be used to verify integrity. */ + if (!(info->enc_delay >= 0 && info->enc_delay <= 2880 && + info->enc_padding >= 0 && info->enc_padding <= 2*1152)) + { + /* Invalid data */ + info->enc_delay = -1; + info->enc_padding = -1; + } +#endif + } + + if (!memcmp(vbrheader, "VBRI", 4)) + { + 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, &tmp, 0x20000, 0); + if(header == 0) + return -6; + + bytecount += tmp; + + if(!mp3headerinfo(info, header)) + return -7; + + DEBUGF("%04x: %04x %04x ", 0, (short)(header >> 16), + (short)(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]); + 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; + + if (info->byte_count <= (ULONG_MAX/8)) + info->bitrate = info->byte_count * 8 / info->file_time; + else + info->bitrate = info->byte_count / (info->file_time >> 3); + + /* 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: %lx\n", info->frame_count); + DEBUGF("Byte count: %lx\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: %lx (%x)\n", i, offset - bytecount, j); + } + } + + return bytecount; +} + +#ifndef __PCTOOL__ +static void long2bytes(unsigned char *buf, long 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; + long bytes; + int cnt; + long progress_chunk = filesize / 50; /* Max is 50%, in 1% increments */ + int progress_cnt = 0; + bool is_vbr = false; + int last_bitrate = 0; + int header_template = 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(fd, &bytes, -1, header_template))) { + mp3headerinfo(&info, header); + + if(!header_template) + header_template = header; + + /* See if this really is a VBR file */ + if(last_bitrate && info.bitrate != last_bitrate) + { + is_vbr = true; + } + last_bitrate = info.bitrate; + + buf_seek(fd, 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); + + if(is_vbr) + return num_frames; + else + { + DEBUGF("Not a VBR file\n"); + return 0; + } +} + +static const char cooltext[] = "Rockbox - rocks your box"; + +/* buf needs to be the audio buffer with TOC generation enabled, + and at least MAX_XING_HEADER_SIZE bytes otherwise */ +int create_xing_header(int fd, long startpos, long filesize, + unsigned char *buf, unsigned long num_frames, + unsigned long rec_time, unsigned long header_template, + void (*progressfunc)(int), bool generate_toc) +{ + struct mp3info info; + unsigned char toc[100]; + unsigned long header = 0; + unsigned long xing_header_template = header_template; + unsigned long filepos; + long pos, last_pos; + long j; + long bytes; + int i; + int index; + + DEBUGF("create_xing_header()\n"); + + if(generate_toc) + { + lseek(fd, startpos, SEEK_SET); + buf_init(); + + /* Generate filepos table */ + last_pos = 0; + filepos = 0; + header = 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++) + { + header = buf_find_next_frame(fd, &bytes, -1, header_template); + filepos += bytes; + mp3headerinfo(&info, header); + buf_seek(fd, info.frame_size-4); + filepos += info.frame_size; + + if(!header_template) + header_template = header; + } + + /* Save a header for later use if header_template is empty. + We only save one header, and we want to save one in the + middle of the stream, just in case the first and the last + headers are corrupt. */ + if(!xing_header_template && i == 1) + xing_header_template = header; + + if(progressfunc) + { + progressfunc(50 + i/2); + } + + /* Fill in the TOC entry */ + /* each toc is a single byte indicating how many 256ths of the + * way through the file, is that percent of the way through the + * song. the easy method, filepos*256/filesize, chokes when + * the upper 8 bits of the file position are nonzero + * (i.e. files over 16mb in size). + */ + if (filepos > (ULONG_MAX/256)) + { + /* instead of multiplying filepos by 256, we divide + * filesize by 256. + */ + toc[i] = filepos / (filesize >> 8); + } + else + { + toc[i] = filepos * 256 / filesize; + } + + DEBUGF("Pos %d: %ld relpos: %ld filepos: %lx tocentry: %x\n", + i, pos, pos-last_pos, filepos, toc[i]); + + last_pos = pos; + } + } + + /* Use the template header and create a new one. + We ignore the Protection bit even if the rest of the stream is + protected. */ + header = xing_header_template & ~(BITRATE_MASK|PROTECTION_MASK|PADDING_MASK); + header |= 8 << 12; /* This gives us plenty of space, 192..576 bytes */ + + if (!mp3headerinfo(&info, header)) + return 0; /* invalid header */ + + if (num_frames == 0 && rec_time) { + /* estimate the number of frames based on the recording time */ + if (rec_time <= ULONG_MAX / info.ft_den) + num_frames = rec_time * info.ft_den / info.ft_num; + else + num_frames = rec_time / info.ft_num * info.ft_den; + } + + /* Clear the frame */ + memset(buf, 0, MAX_XING_HEADER_SIZE); + + /* Write the header to the buffer */ + long2bytes(buf, header); + + /* Calculate position of VBR header */ + if (info.version == MPEG_VERSION1) { + if (info.channel_mode == 3) /* mono */ + index = 21; + else + index = 36; + } + else { + if (info.channel_mode == 3) /* mono */ + index = 13; + else + index = 21; + } + + /* Create the Xing data */ + memcpy(&buf[index], "Xing", 4); + long2bytes(&buf[index+4], (num_frames ? VBR_FRAMES_FLAG : 0) + | (filesize ? VBR_BYTES_FLAG : 0) + | (generate_toc ? VBR_TOC_FLAG : 0)); + index += 8; + if(num_frames) + { + long2bytes(&buf[index], num_frames); + index += 4; + } + + if(filesize) + { + long2bytes(&buf[index], filesize - startpos); + index += 4; + } + + /* Copy the TOC */ + memcpy(buf + index, toc, 100); + + /* And some extra cool info */ + memcpy(buf + index + 100, cooltext, sizeof(cooltext)); + +#ifdef DEBUG + for(i = 0;i < info.frame_size;i++) + { + if(i && !(i % 16)) + DEBUGF("\n"); + + DEBUGF("%02x ", buf[i]); + } +#endif + + return info.frame_size; +} + +#endif diff --git a/apps/mp3data.h b/apps/mp3data.h new file mode 100644 index 0000000..2a6a27a --- /dev/null +++ b/apps/mp3data.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Linus Nielsen Feltzing + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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_VERSION1 0 +#define MPEG_VERSION2 1 +#define MPEG_VERSION2_5 2 + +struct mp3info { + /* Standard MP3 frame header fields */ + int version; + int layer; + bool protection; + int bitrate; + long frequency; + int padding; + int channel_mode; + int mode_extension; + int emphasis; + 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_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]; + 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 */ +}; + +/* Xing header information */ +#define VBR_FRAMES_FLAG 0x01 +#define VBR_BYTES_FLAG 0x02 +#define VBR_TOC_FLAG 0x04 +#define VBR_QUALITY_FLAG 0x08 + +#define MAX_XING_HEADER_SIZE 576 + +unsigned long find_next_frame(int fd, long *offset, long max_offset, + unsigned long last_header); +unsigned long mem_find_next_frame(int startpos, long *offset, long 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, long startpos, long filesize, + unsigned char *buf, unsigned long num_frames, + unsigned long rec_time, unsigned long header_template, + void (*progressfunc)(int), bool generate_toc); + +extern unsigned long bytes2int(unsigned long b0, + unsigned long b1, + unsigned long b2, + unsigned long b3); + +#endif diff --git a/apps/mpeg.c b/apps/mpeg.c index b570f41..6056b68 100644 --- a/apps/mpeg.c +++ b/apps/mpeg.c @@ -26,7 +26,7 @@ #include "debug.h" #include "panic.h" -#include "id3.h" +#include "metadata.h" #include "mpeg.h" #include "audio.h" #include "ata.h" diff --git a/apps/mpeg.h b/apps/mpeg.h index ce2cff0..f5ce613 100644 --- a/apps/mpeg.h +++ b/apps/mpeg.h @@ -22,7 +22,7 @@ #define _MPEG_H_ #include -#include "id3.h" +#include "metadata.h" #define MPEG_SWAP_CHUNKSIZE 0x2000 #define MPEG_HIGH_WATER 2 /* We leave 2 bytes empty because otherwise we diff --git a/apps/onplay.c b/apps/onplay.c index fae86cf..1735fdb 100644 --- a/apps/onplay.c +++ b/apps/onplay.c @@ -37,7 +37,7 @@ #include "kernel.h" #include "keyboard.h" #include "mp3data.h" -#include "id3.h" +#include "metadata.h" #include "screens.h" #include "tree.h" #include "buffer.h" diff --git a/apps/playlist.h b/apps/playlist.h index 345417a..df3bd62 100644 --- a/apps/playlist.h +++ b/apps/playlist.h @@ -25,7 +25,7 @@ #include #include "file.h" #include "kernel.h" -#include "id3.h" +#include "metadata.h" #define PLAYLIST_ATTR_QUEUED 0x01 #define PLAYLIST_ATTR_INSERTED 0x02 diff --git a/apps/plugin.h b/apps/plugin.h index 20724f8..1029431 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -52,7 +52,7 @@ void* plugin_get_buffer(size_t *buffer_size); #include "usb.h" #include "font.h" #include "lcd.h" -#include "id3.h" +#include "metadata.h" #include "sound.h" #include "mpeg.h" #include "audio.h" diff --git a/apps/recorder/albumart.c b/apps/recorder/albumart.c index 29a1ed3..30a4e0c 100644 --- a/apps/recorder/albumart.c +++ b/apps/recorder/albumart.c @@ -23,7 +23,7 @@ #include "sprintf.h" #include "system.h" #include "albumart.h" -#include "id3.h" +#include "metadata.h" #include "gwps.h" #include "buffering.h" #include "dircache.h" diff --git a/apps/recorder/albumart.h b/apps/recorder/albumart.h index e7033c1..52e7c74 100644 --- a/apps/recorder/albumart.h +++ b/apps/recorder/albumart.h @@ -25,7 +25,7 @@ #ifdef HAVE_ALBUMART #include -#include "id3.h" +#include "metadata.h" #include "gwps.h" /* Look for albumart bitmap in the same dir as the track and in its parent dir. diff --git a/apps/recorder/icons.c b/apps/recorder/icons.c index c4f18e8..0b48c12 100644 --- a/apps/recorder/icons.c +++ b/apps/recorder/icons.c @@ -28,7 +28,7 @@ #include "settings.h" -#include "id3.h" +#include "metadata.h" #include "icons.h" const unsigned char bitmap_icons_5x8[][5] = diff --git a/apps/recorder/icons.h b/apps/recorder/icons.h index dca5f29..767e0f2 100644 --- a/apps/recorder/icons.h +++ b/apps/recorder/icons.h @@ -24,6 +24,7 @@ #ifndef PLUGIN #include +#include "metadata.h" #ifdef HAVE_LCD_BITMAP @@ -88,7 +89,7 @@ enum Glyphs_4x8 { extern const unsigned char bitmap_glyphs_4x8[Glyph_4x8Last][4]; #define BM_MPA_L3_M_WIDTH 6 -#ifdef ID3_H + /* This enum is redundant but sort of in keeping with the style */ enum rec_format_18x8 { Format_18x8_AIFF = REC_FORMAT_AIFF, @@ -98,7 +99,7 @@ enum rec_format_18x8 { Format_18x8Last = REC_NUM_FORMATS }; extern const unsigned char bitmap_formats_18x8[Format_18x8Last][18]; -#endif /* ID3_H */ + #endif /* CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING) */ extern const unsigned char bitmap_icons_5x8[Icon5x8Last][5]; diff --git a/apps/recorder/pcm_record.c b/apps/recorder/pcm_record.c index b1ea535..da4e9b7 100644 --- a/apps/recorder/pcm_record.c +++ b/apps/recorder/pcm_record.c @@ -30,7 +30,7 @@ #include "general.h" #include "audio.h" #include "sound.h" -#include "id3.h" +#include "metadata.h" #ifdef HAVE_SPDIF_IN #include "spdif.h" #endif diff --git a/apps/replaygain.c b/apps/replaygain.c index e160a1b..e0bfc8e 100644 --- a/apps/replaygain.c +++ b/apps/replaygain.c @@ -27,7 +27,7 @@ #include #include #include -#include "id3.h" +#include "metadata.h" #include "debug.h" #include "replaygain.h" diff --git a/apps/replaygain.h b/apps/replaygain.h index dbc079b..02ca2e0 100644 --- a/apps/replaygain.h +++ b/apps/replaygain.h @@ -22,7 +22,7 @@ #ifndef _REPLAYGAIN_H #define _REPLAYGAIN_H -#include "id3.h" +#include "metadata.h" long get_replaygain_int(long int_gain); long parse_replaygain(const char* key, const char* value, diff --git a/apps/screens.c b/apps/screens.c index 33c54ab..32edae2 100644 --- a/apps/screens.c +++ b/apps/screens.c @@ -46,7 +46,7 @@ #include "action.h" #include "talk.h" #include "misc.h" -#include "id3.h" +#include "metadata.h" #include "screens.h" #include "debug.h" #include "led.h" diff --git a/apps/scrobbler.c b/apps/scrobbler.c index 3b35e0d..2c6bdf4 100644 --- a/apps/scrobbler.c +++ b/apps/scrobbler.c @@ -27,7 +27,7 @@ http://www.audioscrobbler.net/wiki/Portable_Player_Logging #include "sprintf.h" #include "playback.h" #include "logf.h" -#include "id3.h" +#include "metadata.h" #include "kernel.h" #include "audio.h" #include "buffer.h" diff --git a/apps/tagcache.c b/apps/tagcache.c index ffad383..19469cd 100644 --- a/apps/tagcache.c +++ b/apps/tagcache.c @@ -69,7 +69,7 @@ #include "string.h" #include "usb.h" #include "metadata.h" -#include "id3.h" +#include "metadata.h" #include "tagcache.h" #include "buffer.h" #include "crc32.h" diff --git a/apps/tagcache.h b/apps/tagcache.h index e49b65f..c69e28f 100644 --- a/apps/tagcache.h +++ b/apps/tagcache.h @@ -22,7 +22,7 @@ #ifndef _TAGCACHE_H #define _TAGCACHE_H -#include "id3.h" +#include "metadata.h" /** Note: When adding new tags, make sure to update index_entry_ec in diff --git a/apps/talk.c b/apps/talk.c index 1b2b1e7..2da4cd5 100644 --- a/apps/talk.c +++ b/apps/talk.c @@ -36,7 +36,7 @@ #include "audio.h" #include "lang.h" #include "talk.h" -#include "id3.h" +#include "metadata.h" #include "logf.h" #include "bitswap.h" #include "structec.h" diff --git a/firmware/SOURCES b/firmware/SOURCES index 2cd1ba1..3de2077 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -180,7 +180,6 @@ drivers/tuner/tea5767.c #if CONFIG_CODEC != SWCODEC mp3_playback.c #endif /* CONFIG_CODEC != SWCODEC */ -mp3data.c sound.c #if CONFIG_CODEC == SWCODEC diff --git a/firmware/export/mp3data.h b/firmware/export/mp3data.h deleted file mode 100644 index 2a6a27a..0000000 --- a/firmware/export/mp3data.h +++ /dev/null @@ -1,83 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2002 by Linus Nielsen Feltzing - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * 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_VERSION1 0 -#define MPEG_VERSION2 1 -#define MPEG_VERSION2_5 2 - -struct mp3info { - /* Standard MP3 frame header fields */ - int version; - int layer; - bool protection; - int bitrate; - long frequency; - int padding; - int channel_mode; - int mode_extension; - int emphasis; - 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_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]; - 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 */ -}; - -/* Xing header information */ -#define VBR_FRAMES_FLAG 0x01 -#define VBR_BYTES_FLAG 0x02 -#define VBR_TOC_FLAG 0x04 -#define VBR_QUALITY_FLAG 0x08 - -#define MAX_XING_HEADER_SIZE 576 - -unsigned long find_next_frame(int fd, long *offset, long max_offset, - unsigned long last_header); -unsigned long mem_find_next_frame(int startpos, long *offset, long 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, long startpos, long filesize, - unsigned char *buf, unsigned long num_frames, - unsigned long rec_time, unsigned long header_template, - void (*progressfunc)(int), bool generate_toc); - -extern unsigned long bytes2int(unsigned long b0, - unsigned long b1, - unsigned long b2, - unsigned long b3); - -#endif diff --git a/firmware/mp3data.c b/firmware/mp3data.c deleted file mode 100644 index 80870cd..0000000 --- a/firmware/mp3data.c +++ /dev/null @@ -1,782 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2002 by Daniel Stenberg - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * 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 -#include -#include -#include -#include -#include "debug.h" -#include "logf.h" -#include "mp3data.h" -#include "file.h" -#include "buffer.h" - -// #define DEBUG_VERBOSE - -#define SYNC_MASK (0x7ffL << 21) -#define VERSION_MASK (3L << 19) -#define LAYER_MASK (3L << 17) -#define PROTECTION_MASK (1L << 16) -#define BITRATE_MASK (0xfL << 12) -#define SAMPLERATE_MASK (3L << 10) -#define PADDING_MASK (1L << 9) -#define PRIVATE_MASK (1L << 8) -#define CHANNELMODE_MASK (3L << 6) -#define MODE_EXT_MASK (3L << 4) -#define COPYRIGHT_MASK (1L << 3) -#define ORIGINAL_MASK (1L << 2) -#define EMPHASIS_MASK 3L - -/* MPEG Version table, sorted by version index */ -static const signed char version_table[4] = { - MPEG_VERSION2_5, -1, MPEG_VERSION2, MPEG_VERSION1 -}; - -/* 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 */ -}; - -/* Bitrate pointer table, indexed by version and layer */ -static const short *bitrate_table[3][3] = -{ - {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 unsigned short freq_table[3][3] = -{ - {44100, 48000, 32000}, /* MPEG Version 1 */ - {22050, 24000, 16000}, /* MPEG version 2 */ - {11025, 12000, 8000}, /* MPEG version 2.5 */ -}; - -unsigned long bytes2int(unsigned long b0, - unsigned long b1, - unsigned long b2, - unsigned long b3) -{ - return (((long)(b0 & 0xFF) << (3*8)) | - ((long)(b1 & 0xFF) << (2*8)) | - ((long)(b2 & 0xFF) << (1*8)) | - ((long)(b3 & 0xFF) << (0*8))); -} - -/* 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) == (1L << 19)) /* bad version? */ - return false; - if (!(head & LAYER_MASK)) /* no layer? */ - return false; -#if CONFIG_CODEC != SWCODEC - /* The MAS can't decode layer 1, so treat layer 1 data as invalid */ - if ((head & LAYER_MASK) == LAYER_MASK) - return false; -#endif - 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; - - return true; -} - -static bool mp3headerinfo(struct mp3info *info, unsigned long header) -{ - int bitindex, freqindex; - - /* MPEG Audio Version */ - if ((header & VERSION_MASK) >> 19 >= sizeof(version_table)) - return false; - - info->version = version_table[(header & VERSION_MASK) >> 19]; - if (info->version < 0) - return false; - - /* Layer */ - info->layer = 3 - ((header & LAYER_MASK) >> 17); - if (info->layer == 3) - return false; - - info->protection = (header & PROTECTION_MASK) ? true : false; - - /* Bitrate */ - bitindex = (header & BITRATE_MASK) >> 12; - info->bitrate = bitrate_table[info->version][info->layer][bitindex]; - if(info->bitrate == 0) - return false; - - /* 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 */ - 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; - } - - /* Frametime fraction denominator */ - if (freqindex != 0) { /* 48/32/24/16/12/8 kHz */ - info->ft_den = 1; /* integer number of milliseconds */ - } - else { /* 44.1/22.05/11.025 kHz */ - if (info->layer == 0) /* layer 1 */ - info->ft_den = 147; - else /* layer 2+3 */ - info->ft_den = 49; - } - /* Frametime fraction numerator */ - info->ft_num = 1000 * info->ft_den * info->frame_samples / info->frequency; - - 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: %08lx, 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->ft_num, info->ft_den); -#endif - return true; -} - -static unsigned long __find_next_frame(int fd, long *offset, long max_offset, - unsigned long last_header, - int(*getfunc)(int fd, unsigned char *c)) -{ - unsigned long header=0; - unsigned char tmp; - int i; - - long 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(!getfunc(fd, &tmp)) - return 0; - header |= tmp; - pos++; - } - - do { - header <<= 8; - if(!getfunc(fd, &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; - -#if defined(DEBUG) - if(*offset) - DEBUGF("Warning: skipping %ld bytes of garbage\n", *offset); -#endif - - return header; -} - -static int fileread(int fd, unsigned char *c) -{ - return read(fd, c, 1); -} - -unsigned long find_next_frame(int fd, long *offset, long max_offset, unsigned long last_header) -{ - return __find_next_frame(fd, offset, max_offset, last_header, fileread); -} - -#ifndef __PCTOOL__ -static int fnf_read_index; -static int fnf_buf_len; - -static int buf_getbyte(int fd, unsigned char *c) -{ - if(fnf_read_index < fnf_buf_len) - { - *c = audiobuf[fnf_read_index++]; - return 1; - } - else - { - fnf_buf_len = read(fd, audiobuf, audiobufend - audiobuf); - if(fnf_buf_len < 0) - return -1; - - fnf_read_index = 0; - - if(fnf_buf_len > 0) - { - *c = audiobuf[fnf_read_index++]; - return 1; - } - else - return 0; - } - return 0; -} - -static int buf_seek(int fd, 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, audiobuf, audiobufend - audiobuf); - 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; -} - -static unsigned long buf_find_next_frame(int fd, long *offset, long max_offset, - unsigned long last_header) -{ - return __find_next_frame(fd, offset, max_offset, last_header, buf_getbyte); -} - -static int audiobuflen; -static int mem_pos; -static int mem_cnt; -static int mem_maxlen; - -static int mem_getbyte(int dummy, unsigned char *c) -{ - dummy = dummy; - - *c = audiobuf[mem_pos++]; - if(mem_pos >= audiobuflen) - mem_pos = 0; - - if(mem_cnt++ >= mem_maxlen) - return 0; - else - return 1; -} - -unsigned long mem_find_next_frame(int startpos, long *offset, long max_offset, - unsigned long last_header) -{ - audiobuflen = audiobufend - audiobuf; - mem_pos = startpos; - mem_cnt = 0; - mem_maxlen = max_offset; - - return __find_next_frame(0, offset, max_offset, last_header, mem_getbyte); -} -#endif - -int get_mp3file_info(int fd, struct mp3info *info) -{ - unsigned char frame[1800]; - unsigned char *vbrheader; - unsigned long header; - long bytecount; - int num_offsets; - int frames_per_entry; - int i; - long offset; - int j; - long 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 CONFIG_CODEC==SWCODEC - /* These two are needed for proper LAME gapless MP3 playback */ - info->enc_delay = -1; - info->enc_padding = -1; -#endif - if(!mp3headerinfo(info, header)) - return -2; - - /* OK, we have found a frame. Let's see if it has a Xing header */ - if (info->frame_size-4 >= (int)sizeof(frame)) - { -#if defined(DEBUG) - DEBUGF("Error: Invalid id3 header, frame_size: %d\n", info->frame_size); -#endif - return -8; - } - - 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 (!memcmp(vbrheader, "Xing", 4) - || !memcmp(vbrheader, "Info", 4)) - { - int i = 8; /* Where to start parsing info */ - - /* DEBUGF("Xing/Info header\n"); */ - - /* Remember where in the file the Xing header is */ - info->vbr_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; - - /* Is it a VBR file? */ - info->is_vbr = info->is_xing_vbr = !memcmp(vbrheader, "Xing", 4); - - 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]); - 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; - } - - 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]); - i += 4; - } - - if (info->file_time && info->byte_count) - { - if (info->byte_count <= (ULONG_MAX/8)) - info->bitrate = info->byte_count * 8 / info->file_time; - else - info->bitrate = info->byte_count / (info->file_time >> 3); - } - else - info->bitrate = 0; - - if (vbrheader[7] & VBR_TOC_FLAG) /* Is table-of-contents there? */ - { - info->has_toc = true; - memcpy( info->toc, vbrheader+i, 100 ); - i += 100; - } - if (vbrheader[7] & VBR_QUALITY_FLAG) - { - /* We don't care about this, but need to skip it */ - i += 4; - } -#if CONFIG_CODEC==SWCODEC - i += 21; - info->enc_delay = (vbrheader[i] << 4) | (vbrheader[i + 1] >> 4); - info->enc_padding = ((vbrheader[i + 1] & 0x0f) << 8) | vbrheader[i + 2]; - /* TODO: This sanity checking is rather silly, seeing as how the LAME - header contains a CRC field that can be used to verify integrity. */ - if (!(info->enc_delay >= 0 && info->enc_delay <= 2880 && - info->enc_padding >= 0 && info->enc_padding <= 2*1152)) - { - /* Invalid data */ - info->enc_delay = -1; - info->enc_padding = -1; - } -#endif - } - - if (!memcmp(vbrheader, "VBRI", 4)) - { - 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, &tmp, 0x20000, 0); - if(header == 0) - return -6; - - bytecount += tmp; - - if(!mp3headerinfo(info, header)) - return -7; - - DEBUGF("%04x: %04x %04x ", 0, (short)(header >> 16), - (short)(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]); - 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; - - if (info->byte_count <= (ULONG_MAX/8)) - info->bitrate = info->byte_count * 8 / info->file_time; - else - info->bitrate = info->byte_count / (info->file_time >> 3); - - /* 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: %lx\n", info->frame_count); - DEBUGF("Byte count: %lx\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: %lx (%x)\n", i, offset - bytecount, j); - } - } - - return bytecount; -} - -#ifndef __PCTOOL__ -static void long2bytes(unsigned char *buf, long 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; - long bytes; - int cnt; - long progress_chunk = filesize / 50; /* Max is 50%, in 1% increments */ - int progress_cnt = 0; - bool is_vbr = false; - int last_bitrate = 0; - int header_template = 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(fd, &bytes, -1, header_template))) { - mp3headerinfo(&info, header); - - if(!header_template) - header_template = header; - - /* See if this really is a VBR file */ - if(last_bitrate && info.bitrate != last_bitrate) - { - is_vbr = true; - } - last_bitrate = info.bitrate; - - buf_seek(fd, 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); - - if(is_vbr) - return num_frames; - else - { - DEBUGF("Not a VBR file\n"); - return 0; - } -} - -static const char cooltext[] = "Rockbox - rocks your box"; - -/* buf needs to be the audio buffer with TOC generation enabled, - and at least MAX_XING_HEADER_SIZE bytes otherwise */ -int create_xing_header(int fd, long startpos, long filesize, - unsigned char *buf, unsigned long num_frames, - unsigned long rec_time, unsigned long header_template, - void (*progressfunc)(int), bool generate_toc) -{ - struct mp3info info; - unsigned char toc[100]; - unsigned long header = 0; - unsigned long xing_header_template = header_template; - unsigned long filepos; - long pos, last_pos; - long j; - long bytes; - int i; - int index; - - DEBUGF("create_xing_header()\n"); - - if(generate_toc) - { - lseek(fd, startpos, SEEK_SET); - buf_init(); - - /* Generate filepos table */ - last_pos = 0; - filepos = 0; - header = 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++) - { - header = buf_find_next_frame(fd, &bytes, -1, header_template); - filepos += bytes; - mp3headerinfo(&info, header); - buf_seek(fd, info.frame_size-4); - filepos += info.frame_size; - - if(!header_template) - header_template = header; - } - - /* Save a header for later use if header_template is empty. - We only save one header, and we want to save one in the - middle of the stream, just in case the first and the last - headers are corrupt. */ - if(!xing_header_template && i == 1) - xing_header_template = header; - - if(progressfunc) - { - progressfunc(50 + i/2); - } - - /* Fill in the TOC entry */ - /* each toc is a single byte indicating how many 256ths of the - * way through the file, is that percent of the way through the - * song. the easy method, filepos*256/filesize, chokes when - * the upper 8 bits of the file position are nonzero - * (i.e. files over 16mb in size). - */ - if (filepos > (ULONG_MAX/256)) - { - /* instead of multiplying filepos by 256, we divide - * filesize by 256. - */ - toc[i] = filepos / (filesize >> 8); - } - else - { - toc[i] = filepos * 256 / filesize; - } - - DEBUGF("Pos %d: %ld relpos: %ld filepos: %lx tocentry: %x\n", - i, pos, pos-last_pos, filepos, toc[i]); - - last_pos = pos; - } - } - - /* Use the template header and create a new one. - We ignore the Protection bit even if the rest of the stream is - protected. */ - header = xing_header_template & ~(BITRATE_MASK|PROTECTION_MASK|PADDING_MASK); - header |= 8 << 12; /* This gives us plenty of space, 192..576 bytes */ - - if (!mp3headerinfo(&info, header)) - return 0; /* invalid header */ - - if (num_frames == 0 && rec_time) { - /* estimate the number of frames based on the recording time */ - if (rec_time <= ULONG_MAX / info.ft_den) - num_frames = rec_time * info.ft_den / info.ft_num; - else - num_frames = rec_time / info.ft_num * info.ft_den; - } - - /* Clear the frame */ - memset(buf, 0, MAX_XING_HEADER_SIZE); - - /* Write the header to the buffer */ - long2bytes(buf, header); - - /* Calculate position of VBR header */ - if (info.version == MPEG_VERSION1) { - if (info.channel_mode == 3) /* mono */ - index = 21; - else - index = 36; - } - else { - if (info.channel_mode == 3) /* mono */ - index = 13; - else - index = 21; - } - - /* Create the Xing data */ - memcpy(&buf[index], "Xing", 4); - long2bytes(&buf[index+4], (num_frames ? VBR_FRAMES_FLAG : 0) - | (filesize ? VBR_BYTES_FLAG : 0) - | (generate_toc ? VBR_TOC_FLAG : 0)); - index += 8; - if(num_frames) - { - long2bytes(&buf[index], num_frames); - index += 4; - } - - if(filesize) - { - long2bytes(&buf[index], filesize - startpos); - index += 4; - } - - /* Copy the TOC */ - memcpy(buf + index, toc, 100); - - /* And some extra cool info */ - memcpy(buf + index + 100, cooltext, sizeof(cooltext)); - -#ifdef DEBUG - for(i = 0;i < info.frame_size;i++) - { - if(i && !(i % 16)) - DEBUGF("\n"); - - DEBUGF("%02x ", buf[i]); - } -#endif - - return info.frame_size; -} - -#endif -- cgit v1.1