summaryrefslogtreecommitdiff
path: root/firmware/mp3data.c
diff options
context:
space:
mode:
authorLinus Nielsen Feltzing <linus@haxx.se>2003-03-10 14:55:31 +0000
committerLinus Nielsen Feltzing <linus@haxx.se>2003-03-10 14:55:31 +0000
commita039091187f40d018b6353b8c13de7a01d3a6fe0 (patch)
tree08f7eb86e86e2c61f4d36f9c91731cb93252ba84 /firmware/mp3data.c
parent22cbe938feb48895d7488449835d3ee577399057 (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.c664
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;
+}