diff options
author | Linus Nielsen Feltzing <linus@haxx.se> | 2003-03-10 14:55:31 +0000 |
---|---|---|
committer | Linus Nielsen Feltzing <linus@haxx.se> | 2003-03-10 14:55:31 +0000 |
commit | a039091187f40d018b6353b8c13de7a01d3a6fe0 (patch) | |
tree | 08f7eb86e86e2c61f4d36f9c91731cb93252ba84 /firmware/mp3data.c | |
parent | 22cbe938feb48895d7488449835d3ee577399057 (diff) |
New ID3 and MP3 stream parser, plus not-yet-ready Xing header generation code
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@3410 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/mp3data.c')
-rw-r--r-- | firmware/mp3data.c | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/firmware/mp3data.c b/firmware/mp3data.c new file mode 100644 index 0000000000..8d925041ce --- /dev/null +++ b/firmware/mp3data.c @@ -0,0 +1,664 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2002 by Daniel Stenberg + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* + * Parts of this code has been stolen from the Ample project and was written + * by David Härdeman. It has since been extended and enhanced pretty much by + * all sorts of friendly Rockbox people. + * + * A nice reference for MPEG header info: + * http://rockbox.haxx.se/docs/mpeghdr.html + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include "debug.h" +#include "mp3data.h" +#include "file.h" + +#define DEBUG_VERBOSE + +#define BYTES2INT(b1,b2,b3,b4) (((b1 & 0xFF) << (3*8)) | \ + ((b2 & 0xFF) << (2*8)) | \ + ((b3 & 0xFF) << (1*8)) | \ + ((b4 & 0xFF) << (0*8))) + +#define SYNC_MASK (0x7ff << 21) +#define VERSION_MASK (3 << 19) +#define LAYER_MASK (3 << 17) +#define PROTECTION_MASK (1 << 16) +#define BITRATE_MASK (0xf << 12) +#define SAMPLERATE_MASK (3 << 10) +#define PADDING_MASK (1 << 9) +#define PRIVATE_MASK (1 << 8) +#define CHANNELMODE_MASK (3 << 6) +#define MODE_EXT_MASK (3 << 4) +#define COPYRIGHT_MASK (1 << 3) +#define ORIGINAL_MASK (1 << 2) +#define EMPHASIS_MASK 3 + +/* Table of bitrates for MP3 files, all values in kilo. + * Indexed by version, layer and value of bit 15-12 in header. + */ +const int bitrate_table[2][3][16] = +{ + { + {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, + {0,32,48,56, 64,80, 96, 112,128,160,192,224,256,320,384,0}, + {0,32,40,48, 56,64, 80, 96, 112,128,160,192,224,256,320,0} + }, + { + {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0}, + {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0}, + {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0} + } +}; + +/* Table of samples per frame for MP3 files. + * Indexed by layer. Multiplied with 1000. + */ +const int bs[3] = {384000, 1152000, 1152000}; + +/* Table of sample frequency for MP3 files. + * Indexed by version and layer. + */ + +const int freqtab[][4] = +{ + {11025, 12000, 8000, 0}, /* MPEG version 2.5 */ + {44100, 48000, 32000, 0}, /* MPEG Version 1 */ + {22050, 24000, 16000, 0}, /* MPEG version 2 */ +}; + +/* check if 'head' is a valid mp3 frame header */ +static bool is_mp3frameheader(unsigned long head) +{ + if ((head & SYNC_MASK) != (unsigned long)SYNC_MASK) /* bad sync? */ + return false; + if ((head & VERSION_MASK) == (1 << 19)) /* bad version? */ + return false; + if (!(head & LAYER_MASK)) /* no layer? */ + return false; + if ((head & BITRATE_MASK) == BITRATE_MASK) /* bad bitrate? */ + return false; + if (!(head & BITRATE_MASK)) /* no bitrate? */ + return false; + if ((head & SAMPLERATE_MASK) == SAMPLERATE_MASK) /* bad sample rate? */ + return false; + if (((head >> 19) & 1) == 1 && + ((head >> 17) & 3) == 3 && + ((head >> 16) & 1) == 1) + return false; + if ((head & 0xffff0000) == 0xfffe0000) + return false; + + return true; +} + +static bool mp3headerinfo(struct mp3info *info, unsigned long header) +{ + int bittable = 0; + int bitindex; + int freqindex; + + /* MPEG Audio Version */ + switch(header & VERSION_MASK) { + case 0: + /* MPEG version 2.5 is not an official standard */ + info->version = MPEG_VERSION2_5; + bittable = MPEG_VERSION2 - 1; /* use the V2 bit rate table */ + break; + + case (1 << 19): + return false; + + case (2 << 19): + /* MPEG version 2 (ISO/IEC 13818-3) */ + info->version = MPEG_VERSION2; + bittable = MPEG_VERSION2 - 1; + break; + + case (3 << 19): + /* MPEG version 1 (ISO/IEC 11172-3) */ + info->version = MPEG_VERSION1; + bittable = MPEG_VERSION1 - 1; + break; + } + + switch(header & LAYER_MASK) { + case 0: + return false; + case (1 << 17): + info->layer = 2; + break; + case (2 << 17): + info->layer = 1; + break; + case (3 << 17): + info->layer = 0; + break; + } + + info->protection = (header & PROTECTION_MASK)?true:false; + + /* Bitrate */ + bitindex = (header & 0xf000) >> 12; + info->bitrate = bitrate_table[bittable][info->layer][bitindex]; + if(info->bitrate == 0) + return false; + + /* Sampling frequency */ + freqindex = (header & 0x0C00) >> 10; + info->frequency = freqtab[info->version][freqindex]; + if(info->frequency == 0) + return false; + + info->padding = (header & 0x0200)?1:0; + + /* Calculate number of bytes, calculation depends on layer */ + switch(info->layer) { + case 0: + info->frame_size = info->bitrate * 48000; + info->frame_size /= + freqtab[info->version][freqindex] << bittable; + break; + case 1: + case 2: + info->frame_size = info->bitrate * 144000; + info->frame_size /= + freqtab[info->version][freqindex] << bittable; + break; + default: + info->frame_size = 1; + } + + info->frame_size += info->padding; + + /* Calculate time per frame */ + info->frame_time = bs[info->layer] / + (freqtab[info->version][freqindex] << bittable); + + info->channel_mode = (header & 0xc0) >> 6; + info->mode_extension = (header & 0x30) >> 4; + info->emphasis = header & 3; + +#ifdef DEBUG_VERBOSE + DEBUGF( "Header: %08x, Ver %d, lay %d, bitr %d, freq %d, " + "chmode %d, mode_ext %d, emph %d, bytes: %d time: %d\n", + header, info->version, info->layer, info->bitrate, info->frequency, + info->channel_mode, info->mode_extension, + info->emphasis, info->frame_size, info->frame_time); +#endif + return true; +} + +unsigned long find_next_frame(int fd, int *offset, int max_offset, unsigned long last_header) +{ + unsigned long header=0; + unsigned char tmp; + int i; + + int pos = 0; + + /* We remember the last header we found, to use as a template to see if + the header we find has the same frequency, layer etc */ + last_header &= 0xffff0c00; + + /* Fill up header with first 24 bits */ + for(i = 0; i < 3; i++) { + header <<= 8; + if(!read(fd, &tmp, 1)) + return 0; + header |= tmp; + pos++; + } + + do { + header <<= 8; + if(!read(fd, &tmp, 1)) + return 0; + header |= tmp; + pos++; + if(max_offset > 0 && pos > max_offset) + return 0; + } while(!is_mp3frameheader(header) || + (last_header?((header & 0xffff0c00) != last_header):false)); + + *offset = pos - 4; + +#ifdef DEBUG + if(*offset) + DEBUGF("Warning: skipping %d bytes of garbage\n", *offset); +#endif + + return header; +} + +#ifdef SIMULATOR +unsigned char mp3buf[0x100000]; +unsigned char mp3end[1]; +#else +extern unsigned char mp3buf[]; +extern unsigned char mp3end[]; +#endif +static int fnf_read_index; +static int fnf_buf_len; + +static int fd; + +static int buf_getbyte(unsigned char *c) +{ + if(fnf_read_index < fnf_buf_len) + { + *c = mp3buf[fnf_read_index++]; + return 1; + } + else + { + fnf_buf_len = read(fd, mp3buf, mp3end - mp3buf); + if(fnf_buf_len < 0) + return -1; + + fnf_read_index = 0; + + if(fnf_buf_len > 0) + { + *c = mp3buf[fnf_read_index++]; + return 1; + } + else + return 0; + } + return 0; +} + +static int buf_seek(int len) +{ + fnf_read_index += len; + if(fnf_read_index > fnf_buf_len) + { + len = fnf_read_index - fnf_buf_len; + + fnf_buf_len = read(fd, mp3buf, mp3end - mp3buf); + if(fnf_buf_len < 0) + return -1; + + fnf_read_index = 0; + fnf_read_index += len; + } + + if(fnf_read_index > fnf_buf_len) + { + return -1; + } + else + return 0; +} + +static void buf_init(void) +{ + fnf_buf_len = 0; + fnf_read_index = 0; +} + +unsigned long buf_find_next_frame(int *offset, int max_offset, + unsigned long last_header) +{ + unsigned long header=0; + unsigned char tmp; + int i; + + int pos = 0; + + /* We remember the last header we found, to use as a template to see if + the header we find has the same frequency, layer etc */ + last_header &= 0xffff0c00; + + /* Fill up header with first 24 bits */ + for(i = 0; i < 3; i++) { + header <<= 8; + if(!buf_getbyte(&tmp)) + return 0; + header |= tmp; + pos++; + } + + do { + header <<= 8; + if(!buf_getbyte(&tmp)) + return 0; + header |= tmp; + pos++; + if(max_offset > 0 && pos > max_offset) + return 0; + } while(!is_mp3frameheader(header) || + (last_header?((header & 0xffff0c00) != last_header):false)); + + *offset = pos - 4; + +#ifdef DEBUG + if(*offset) + DEBUGF("Warning: skipping %d bytes of garbage\n", *offset); +#endif + + return header; +} + +int get_mp3file_info(int fd, struct mp3info *info) +{ + unsigned char frame[1024]; + unsigned char *vbrheader; + unsigned long header; + int bytecount; + int num_offsets; + int frames_per_entry; + int i; + int offset; + int j; + int tmp; + + header = find_next_frame(fd, &bytecount, 0x20000, 0); + /* Quit if we haven't found a valid header within 128K */ + if(header == 0) + return -1; + + memset(info, 0, sizeof(struct mp3info)); + if(!mp3headerinfo(info, header)) + return -2; + + /* OK, we have found a frame. Let's see if it has a Xing header */ + if(read(fd, frame, info->frame_size-4) < 0) + return -3; + + /* calculate position of VBR header */ + if ( info->version == MPEG_VERSION1 ) { + if (info->channel_mode == 3) /* mono */ + vbrheader = frame + 17; + else + vbrheader = frame + 32; + } + else { + if (info->channel_mode == 3) /* mono */ + vbrheader = frame + 9; + else + vbrheader = frame + 17; + } + + if (vbrheader[0] == 'X' && + vbrheader[1] == 'i' && + vbrheader[2] == 'n' && + vbrheader[3] == 'g') + { + int i = 8; /* Where to start parsing info */ + + DEBUGF("Xing header\n"); + + /* Remember where in the file the Xing header is */ + info->xing_header_pos = lseek(fd, 0, SEEK_CUR) - info->frame_size; + + /* We want to skip the Xing frame when playing the stream */ + bytecount += info->frame_size; + + /* Now get the next frame to find out the real info about + the mp3 stream */ + header = find_next_frame(fd, &tmp, 0x20000, 0); + if(header == 0) + return -4; + + if(!mp3headerinfo(info, header)) + return -5; + + /* Yes, it is a VBR file */ + info->is_vbr = true; + info->is_xing_vbr = true; + + if(vbrheader[7] & VBR_FRAMES_FLAG) /* Is the frame count there? */ + { + info->frame_count = BYTES2INT(vbrheader[i], vbrheader[i+1], + vbrheader[i+2], vbrheader[i+3]); + info->file_time = info->frame_count * info->frame_time; + i += 4; + } + + if(vbrheader[7] & VBR_BYTES_FLAG) /* Is byte count there? */ + { + info->byte_count = BYTES2INT(vbrheader[i], vbrheader[i+1], + vbrheader[i+2], vbrheader[i+3]); + info->bitrate = info->byte_count * 8 / info->file_time; + i += 4; + } + + if(vbrheader[7] & VBR_TOC_FLAG) /* Is table-of-contents there? */ + { + memcpy( info->toc, vbrheader+i, 100 ); + } + } + + if (vbrheader[0] == 'V' && + vbrheader[1] == 'B' && + vbrheader[2] == 'R' && + vbrheader[3] == 'I') + { + DEBUGF("VBRI header\n"); + + /* We want to skip the VBRI frame when playing the stream */ + bytecount += info->frame_size; + + /* Now get the next frame to find out the real info about + the mp3 stream */ + header = find_next_frame(fd, &bytecount, 0x20000, 0); + if(header == 0) + return -6; + + if(!mp3headerinfo(info, header)) + return -7; + + DEBUGF("%04x: %04x %04x ", 0, header >> 16, header & 0xffff); + for(i = 4;i < (int)sizeof(frame)-4;i+=2) { + if(i % 16 == 0) { + DEBUGF("\n%04x: ", i-4); + } + DEBUGF("%04x ", (frame[i-4] << 8) | frame[i-4+1]); + } + + DEBUGF("\n"); + + /* Yes, it is a FhG VBR file */ + info->is_vbr = true; + info->is_vbri_vbr = true; + info->has_toc = false; /* We don't parse the TOC (yet) */ + + info->byte_count = BYTES2INT(vbrheader[10], vbrheader[11], + vbrheader[12], vbrheader[13]); + info->frame_count = BYTES2INT(vbrheader[14], vbrheader[15], + vbrheader[16], vbrheader[17]); + + info->file_time = info->frame_count * info->frame_time; + info->bitrate = info->byte_count * 8 / info->file_time; + + /* We don't parse the TOC, since we don't yet know how to (FIXME) */ + num_offsets = BYTES2INT(0, 0, vbrheader[18], vbrheader[19]); + frames_per_entry = BYTES2INT(0, 0, vbrheader[24], vbrheader[25]); + DEBUGF("Frame size (%dkpbs): %d bytes (0x%x)\n", + info->bitrate, info->frame_size, info->frame_size); + DEBUGF("Frame count: %x\n", info->frame_count); + DEBUGF("Byte count: %x\n", info->byte_count); + DEBUGF("Offsets: %d\n", num_offsets); + DEBUGF("Frames/entry: %d\n", frames_per_entry); + + offset = 0; + + for(i = 0;i < num_offsets;i++) + { + j = BYTES2INT(0, 0, vbrheader[26+i*2], vbrheader[27+i*2]); + offset += j; + DEBUGF("%03d: %x (%x)\n", i, offset - bytecount, j); + } + } + + /* Is it a LAME Info frame? */ + if (vbrheader[0] == 'I' && + vbrheader[1] == 'n' && + vbrheader[2] == 'f' && + vbrheader[3] == 'o') + { + /* Make sure we skip this frame in playback */ + bytecount += info->frame_size; + } + + return bytecount; +} + +/* This is an MP3 header, 128kbit/s, 44.1kHz, with silence */ +static const unsigned char xing_frame_header[] = { + 0xff, 0xfa, 0x90, 0x64, 0x86, 0x1f +}; + +static const char cooltext[] = "Rockbox rocks"; + +static void int2bytes(unsigned char *buf, int val) +{ + buf[0] = (val >> 24) & 0xff; + buf[1] = (val >> 16) & 0xff; + buf[2] = (val >> 8) & 0xff; + buf[3] = val & 0xff; +} + +int count_mp3_frames(int fd, int startpos, int filesize, + void (*progressfunc)(int)) +{ + unsigned long header = 0; + struct mp3info info; + int num_frames; + int bytes; + int cnt; + int progress_chunk = filesize / 50; /* Max is 50%, in 1% increments */ + int progress_cnt = 0; + + if(lseek(fd, startpos, SEEK_SET) < 0) + return -1; + + buf_init(); + + /* Find out the total number of frames */ + num_frames = 0; + cnt = 0; + + while((header = buf_find_next_frame(&bytes, -1, header))) { + mp3headerinfo(&info, header); + buf_seek(info.frame_size-4); + num_frames++; + if(progressfunc) + { + cnt += bytes + info.frame_size; + if(cnt > progress_chunk) + { + progress_cnt++; + progressfunc(progress_cnt); + cnt = 0; + } + } + } + DEBUGF("Total number of frames: %d\n", num_frames); + + return num_frames; +} + +int create_xing_header(int fd, int startpos, int filesize, + unsigned char *buf, int num_frames, + void (*progressfunc)(int)) +{ + unsigned long header = 0; + struct mp3info info; + int pos, last_pos; + int i, j; + int bytes; + int filepos; + int tocentry; + int x; + + DEBUGF("create_xing_header()\n"); + + /* Create the frame header */ + memset(buf, 0, 417); + memcpy(buf, xing_frame_header, 6); + + lseek(fd, startpos, SEEK_SET); + buf_init(); + + buf[36] = 'X'; + buf[36+1] = 'i'; + buf[36+2] = 'n'; + buf[36+3] = 'g'; + int2bytes(&buf[36+4], (VBR_FRAMES_FLAG | VBR_BYTES_FLAG | VBR_TOC_FLAG)); + int2bytes(&buf[36+8], num_frames); + int2bytes(&buf[36+12], filesize - startpos); + + /* Generate filepos table */ + last_pos = 0; + filepos = 0; + header = 0; + x = 0; + for(i = 0;i < 100;i++) { + /* Calculate the absolute frame number for this seek point */ + pos = i * num_frames / 100; + + /* Advance from the last seek point to this one */ + for(j = 0;j < pos - last_pos;j++) + { + DEBUGF("fpos: %x frame no: %x ", filepos, x++); + header = buf_find_next_frame(&bytes, -1, header); + mp3headerinfo(&info, header); + buf_seek(info.frame_size-4); + filepos += info.frame_size; + } + + if(progressfunc) + { + progressfunc(50 + i/2); + } + + tocentry = filepos * 256 / filesize; + + DEBUGF("Pos %d: %d relpos: %d filepos: %x tocentry: %x\n", + i, pos, pos-last_pos, filepos, tocentry); + + /* Fill in the TOC entry */ + buf[36+16+i] = tocentry; + + last_pos = pos; + } + + memcpy(buf+152, cooltext, sizeof(cooltext)); + +#ifdef DEBUG + for(i = 0;i < 417;i++) + { + if(i && !(i % 16)) + DEBUGF("\n"); + + DEBUGF("%02x ", buf[i]); + } +#endif + + return 0; +} |