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
This commit is contained in:
Linus Nielsen Feltzing 2003-03-10 14:55:31 +00:00
parent 22cbe938fe
commit a039091187
8 changed files with 1016 additions and 425 deletions

View file

@ -39,15 +39,18 @@ struct mp3entry {
unsigned int first_frame_offset; /* Byte offset to first real MP3 frame.
Used for skipping leading garbage to
avoid gaps between tracks. */
unsigned int xing_header_pos;
unsigned int filesize; /* in bytes */
unsigned int length; /* song length */
unsigned int elapsed; /* ms played */
/* MP3 stream specific info */
long bpf; /* bytes per frame */
long tpf; /* time per frame */
/* Xing VBR fields */
bool vbr;
unsigned char vbrflags;
bool has_toc; /* True if there is a VBR header in the file */
unsigned char toc[100];/* table of contents */
/* these following two fields are used for local buffering */
@ -59,10 +62,6 @@ struct mp3entry {
int index; /* playlist index */
};
#define VBR_FRAMES_FLAG 0x01
#define VBR_BYTES_FLAG 0x02
#define VBR_TOC_FLAG 0x04
enum {
ID3_VER_1_0 = 1,
ID3_VER_1_1,

66
firmware/export/mp3data.h Normal file
View file

@ -0,0 +1,66 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Linus Nielsen Feltzing
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _MP3DATA_H_
#define _MP3DATA_H_
#define MPEG_VERSION2_5 0
#define MPEG_VERSION1 1
#define MPEG_VERSION2 2
struct mp3info {
/* Standard MP3 frame header fields */
int version;
int layer;
bool protection;
int bitrate;
int frequency;
int padding;
int channel_mode;
int mode_extension;
int emphasis;
int frame_size; /* Frame size in bytes */
int frame_time; /* Frame duration in milliseconds */
bool is_vbr; /* True if the file is VBR */
bool has_toc; /* True if there is a VBR header in the file */
bool is_xing_vbr; /* True if the VBR header is of Xing type */
bool is_vbri_vbr; /* True if the VBR header is of VBRI type */
unsigned char toc[100];
int frame_count; /* Number of frames in the file (if VBR) */
int byte_count; /* File size in bytes */
int file_time; /* Length of the whole file in milliseconds */
int xing_header_pos;
};
/* Xing header information */
#define VBR_FRAMES_FLAG 0x01
#define VBR_BYTES_FLAG 0x02
#define VBR_TOC_FLAG 0x04
unsigned long find_next_frame(int fd, int *offset, int max_offset, unsigned long last_header);
int get_mp3file_info(int fd, struct mp3info *info);
int count_mp3_frames(int fd, int startpos, int filesize,
void (*progressfunc)(int));
int create_xing_header(int fd, int startpos, int filesize,
unsigned char *buf, int num_frames,
void (*progressfunc)(int));
#endif

View file

@ -90,6 +90,7 @@ unsigned long mpeg_num_recorded_bytes(void);
#endif
void mpeg_get_debugdata(struct mpeg_debug *dbgdata);
void mpeg_set_buffer_margin(int seconds);
int mpeg_create_xing_header(char *filename, void (*progressfunc)(int));
#define SOUND_VOLUME 0
#define SOUND_BASS 1

View file

@ -22,9 +22,6 @@
* by David Härdeman. It has since been extended and enhanced pretty much by
* all sorts of friendly Rockbox people.
*
* A nice reference for MPEG header info:
* http://rockbox.haxx.se/docs/mpeghdr.html
*
*/
#include <stdio.h>
@ -37,6 +34,7 @@
#include "atoi.h"
#include "id3.h"
#include "mp3data.h"
#define UNSYNC(b0,b1,b2,b3) (((b0 & 0x7F) << (3*7)) | \
((b1 & 0x7F) << (2*7)) | \
@ -48,38 +46,6 @@
((b2 & 0xFF) << (1*8)) | \
((b3 & 0xFF) << (0*8)))
/* Table of bitrates for MP3 files, all values in kilo.
* Indexed by version, layer and value of bit 15-12 in header.
*/
const int bitrate_table[2][3][16] =
{
{
{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0},
{0,32,48,56, 64,80, 96, 112,128,160,192,224,256,320,384,0},
{0,32,40,48, 56,64, 80, 96, 112,128,160,192,224,256,320,0}
},
{
{0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0},
{0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0},
{0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0}
}
};
/* Table of samples per frame for MP3 files.
* Indexed by layer. Multiplied with 1000.
*/
const int bs[4] = {0, 384000, 1152000, 1152000};
/* Table of sample frequency for MP3 files.
* Indexed by version and layer.
*/
const int freqtab[][4] =
{
{11025, 12000, 8000, 0}, /* MPEG version 2.5 */
{44100, 48000, 32000, 0}, /* MPEG Version 1 */
{22050, 24000, 16000, 0}, /* MPEG version 2 */
};
/* Checks to see if the passed in string is a 16-bit wide Unicode v2
string. If it is, we attempt to convert it to a 8-bit ASCII string
(for valid 8-bit ASCII characters). If it's not unicode, we leave
@ -168,6 +134,7 @@ static bool setid3v1title(int fd, struct mp3entry *entry)
if (strncmp(buffer, "TAG", 3))
return false;
entry->id3v1len = 128;
entry->id3version = ID3_VER_1_0;
for (i=0; i < (int)sizeof offsets; i++) {
@ -239,6 +206,8 @@ static void setid3v2title(int fd, struct mp3entry *entry)
char *tracknum = NULL;
int bytesread = 0;
int buffersize = sizeof(entry->id3v2buf);
int flags;
int skip;
/* Bail out if the tag is shorter than 10 bytes */
if(entry->id3v2len < 10)
@ -275,17 +244,34 @@ static void setid3v2title(int fd, struct mp3entry *entry)
}
entry->id3version = version;
/* Skip the extended header if it is present */
if(version >= ID3_VER_2_4) {
if(header[5] & 0x40) {
if(4 != read(fd, header, 4))
return;
framelen = UNSYNC(header[0], header[1],
header[2], header[3]);
lseek(fd, framelen - 4, SEEK_CUR);
}
}
/*
* We must have at least minframesize bytes left for the
* remaining frames to be interesting
*/
while(size > minframesize) {
flags = 0;
/* Read frame header and check length */
if(version >= ID3_VER_2_3) {
if(10 != read(fd, header, 10))
return;
/* Adjust for the 10 bytes we read */
size -= 10;
flags = BYTES2INT(0, 0, header[8], header[9]);
if (version >= ID3_VER_2_4) {
framelen = UNSYNC(header[4], header[5],
@ -311,6 +297,33 @@ static void setid3v2title(int fd, struct mp3entry *entry)
if(framelen == 0)
return;
if(flags)
{
skip = 0;
if(flags & 0x0040) /* Grouping identity */
skip++;
if(flags & 0x000e) /* Compression, encryption or
unsynchronization */
{
/* Skip it using the total size in case
it was truncated */
size -= totframelen;
lseek(fd, totframelen, SEEK_CUR);
continue;
}
if(flags & 0x0001) /* Data length indicator */
skip += 4;
if(skip)
{
lseek(fd, skip, SEEK_CUR);
framelen -= skip;
}
}
/* If the frame is larger than the remaining buffer space we try
to read as much as would fit in the buffer */
if(framelen >= buffersize - bufferpos)
@ -404,6 +417,7 @@ static int getid3v2len(int fd)
else
offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10;
DEBUGF("ID3V2 Length: 0x%x\n", offset);
return offset;
}
@ -419,29 +433,6 @@ static int getfilesize(int fd)
return size;
}
/* check if 'head' is a valid mp3 frame header */
static bool mp3frameheader(unsigned long head)
{
if ((head & 0xffe00000) != 0xffe00000) /* bad sync? */
return false;
if (!((head >> 17) & 3)) /* no layer? */
return false;
if (((head >> 12) & 0xf) == 0xf) /* bad bitrate? */
return false;
if (!((head >> 12) & 0xf)) /* no bitrate? */
return false;
if (((head >> 10) & 0x3) == 0x3) /* bad sample rate? */
return false;
if (((head >> 19) & 1) == 1 &&
((head >> 17) & 3) == 3 &&
((head >> 16) & 1) == 1)
return false;
if ((head & 0xffff0000) == 0xfffe0000)
return false;
return true;
}
/*
* Calculates the length (in milliseconds) of an MP3 file.
*
@ -456,263 +447,48 @@ static bool mp3frameheader(unsigned long head)
static int getsonglength(int fd, struct mp3entry *entry)
{
unsigned int filetime = 0;
unsigned long header=0;
unsigned char tmp;
unsigned char frame[156];
unsigned char* xing;
enum {
MPEG_VERSION2_5,
MPEG_VERSION1,
MPEG_VERSION2
} version;
int layer;
int bitindex;
int bitrate;
int freqindex;
int frequency;
int chmode;
struct mp3info info;
int bytecount;
int bytelimit;
int bittable; /* which bitrate table to use */
bool header_found = false;
long bpf;
long tpf;
/* Start searching after ID3v2 header */
if(-1 == lseek(fd, entry->id3v2len, SEEK_SET))
return 0;
/* Fill up header with first 24 bits */
for(version = 0; version < 3; version++) {
header <<= 8;
if(!read(fd, &tmp, 1))
return 0;
header |= tmp;
}
/* Loop trough file until we find a frame header */
bytecount = entry->id3v2len - 1;
bytelimit = entry->id3v2len + 0x20000;
restart:
do {
header <<= 8;
if(!read(fd, &tmp, 1))
return 0;
header |= tmp;
/* Quit if we haven't found a valid header within 128K */
bytecount++;
if(bytecount > bytelimit)
return 0;
} while(!mp3frameheader(header));
/*
* Some files are filled with garbage in the beginning,
* if the bitrate index of the header is binary 1111
* that is a good indicator
*/
if((header & 0xF000) == 0xF000)
goto restart;
bytecount = get_mp3file_info(fd, &info);
/* MPEG Audio Version */
switch((header & 0x180000) >> 19) {
case 0:
/* MPEG version 2.5 is not an official standard */
version = MPEG_VERSION2_5;
bittable = MPEG_VERSION2; /* use the V2 bit rate table */
break;
DEBUGF("Space between ID3V2 tag and first audio frame: 0x%x bytes\n",
bytecount);
case 2:
/* MPEG version 2 (ISO/IEC 13818-3) */
version = MPEG_VERSION2;
bittable = MPEG_VERSION2;
break;
case 3:
/* MPEG version 1 (ISO/IEC 11172-3) */
version = MPEG_VERSION1;
bittable = MPEG_VERSION1;
break;
default:
goto restart;
}
/* Layer */
switch((header & 0x060000) >> 17) {
case 1:
layer = 3;
break;
case 2:
layer = 2;
break;
case 3:
layer = 1;
break;
default:
goto restart;
}
/* Bitrate */
bitindex = (header & 0xF000) >> 12;
bitrate = bitrate_table[bittable-1][layer-1][bitindex];
if(bitrate == 0)
goto restart;
/* Sampling frequency */
freqindex = (header & 0x0C00) >> 10;
frequency = freqtab[version][freqindex];
if(frequency == 0)
goto restart;
#ifdef DEBUG_VERBOSE
DEBUGF( "Version %i, lay %i, biti %i, bitr %i, freqi %i, freq %i, chmode %d\n",
version, layer, bitindex, bitrate, freqindex, frequency, chmode);
#endif
entry->version = version;
entry->layer = layer;
entry->frequency = frequency;
/* Calculate bytes per frame, calculation depends on layer */
switch(layer) {
case 1:
bpf = bitrate_table[bittable - 1][layer - 1][bitindex];
bpf *= 48000;
bpf /= freqtab[version][freqindex] << (bittable - 1);
break;
case 2:
case 3:
bpf = bitrate_table[bittable - 1][layer - 1][bitindex];
bpf *= 144000;
bpf /= freqtab[version][freqindex] << (bittable - 1);
break;
default:
bpf = 1;
}
/* Calculate time per frame */
tpf = bs[layer] / (freqtab[version][freqindex] << (bittable - 1));
entry->bpf = bpf;
entry->tpf = tpf;
/* OK, we have found a frame. Let's see if it has a Xing header */
if(read(fd, frame, sizeof frame) < 0)
if(bytecount < 0)
return -1;
bytecount += entry->id3v2len;
/* Channel mode (stereo/mono) */
chmode = (header & 0xc0) >> 6;
/* calculate position of Xing VBR header */
if ( version == 1 ) {
if ( chmode == 3 ) /* mono */
xing = frame + 17;
else
xing = frame + 32;
}
else {
if ( chmode == 3 ) /* mono */
xing = frame + 9;
else
xing = frame + 17;
}
if (xing[0] == 'X' &&
xing[1] == 'i' &&
xing[2] == 'n' &&
xing[3] == 'g')
{
int i = 8; /* Where to start parsing info */
/* Yes, it is a VBR file */
entry->vbr = true;
entry->vbrflags = xing[7];
if (entry->vbrflags & VBR_FRAMES_FLAG) /* Is the frame count there? */
{
int framecount = (xing[i] << 24) | (xing[i+1] << 16) |
(xing[i+2] << 8) | xing[i+3];
filetime = framecount * tpf;
i += 4;
}
if (entry->vbrflags & VBR_BYTES_FLAG) /* is byte count there? */
{
int bytecount = (xing[i] << 24) | (xing[i+1] << 16) |
(xing[i+2] << 8) | xing[i+3];
bitrate = bytecount * 8 / filetime;
i += 4;
}
if (entry->vbrflags & VBR_TOC_FLAG) /* is table-of-contents there? */
{
memcpy( entry->toc, xing+i, 100 );
}
/* Make sure we skip this frame in playback */
bytecount += bpf;
header_found = true;
}
if (xing[0] == 'V' &&
xing[1] == 'B' &&
xing[2] == 'R' &&
xing[3] == 'I')
{
int framecount;
int bytecount;
/* Yes, it is a FhG VBR file */
entry->vbr = true;
entry->vbrflags = 0;
bytecount = (xing[10] << 24) | (xing[11] << 16) |
(xing[12] << 8) | xing[13];
framecount = (xing[14] << 24) | (xing[15] << 16) |
(xing[16] << 8) | xing[17];
filetime = framecount * tpf;
bitrate = bytecount * 8 / filetime;
/* We don't parse the TOC, since we don't yet know how to (FIXME) */
/* Make sure we skip this frame in playback */
bytecount += bpf;
header_found = true;
}
/* Is it a LAME Info frame? */
if (xing[0] == 'I' &&
xing[1] == 'n' &&
xing[2] == 'f' &&
xing[3] == 'o')
{
/* Make sure we skip this frame in playback */
bytecount += bpf;
header_found = true;
}
entry->bitrate = bitrate;
entry->bitrate = info.bitrate;
/* If the file time hasn't been established, this may be a fixed
rate MP3, so just use the default formula */
filetime = info.file_time;
if(filetime == 0)
{
/*
* Now song length is
* ((filesize)/(bytes per frame))*(time per frame)
*/
filetime = entry->filesize/bpf*tpf;
filetime = entry->filesize/info.frame_size*info.frame_time;
}
entry->tpf = info.frame_time;
entry->bpf = info.frame_size;
entry->vbr = info.is_vbr;
entry->has_toc = info.has_toc;
memcpy(entry->toc, info.toc, sizeof(info.toc));
entry->xing_header_pos = info.xing_header_pos;
/* Update the seek point for the first playable frame */
entry->first_frame_offset = bytecount;
DEBUGF("First frame is at %x\n", entry->first_frame_offset);
@ -720,7 +496,6 @@ static int getsonglength(int fd, struct mp3entry *entry)
return filetime;
}
/*
* Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc)
* about an MP3 file and updates it's entry accordingly.
@ -750,6 +525,10 @@ bool mp3info(struct mp3entry *entry, char *filename)
setid3v2title(fd, entry);
entry->length = getsonglength(fd, entry);
/* Subtract the meta information from the file size to get
the true size of the MP3 stream */
entry->filesize -= entry->first_frame_offset;
/* only seek to end of file if no id3v2 tags were found */
if (!entry->id3v2len) {
if(!entry->title)

664
firmware/mp3data.c Normal file
View file

@ -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;
}

View file

@ -26,6 +26,7 @@
#include "string.h"
#include <kernel.h>
#include "thread.h"
#include "mp3data.h"
#ifndef SIMULATOR
#include "i2c.h"
#include "mas.h"
@ -302,7 +303,7 @@ static void remove_all_tags(void)
static void set_elapsed(struct mp3entry* id3)
{
if ( id3->vbr ) {
if ( id3->vbrflags & VBR_TOC_FLAG ) {
if ( id3->has_toc ) {
/* calculate elapsed time using TOC */
int i;
unsigned int remainder, plen, relpos, nextpos;
@ -1482,7 +1483,7 @@ static void mpeg_thread(void)
if (id3->vbr)
{
if (id3->vbrflags & VBR_TOC_FLAG)
if (id3->has_toc)
{
/* Use the TOC to find the new position */
unsigned int percent, remainder;
@ -1528,10 +1529,10 @@ static void mpeg_thread(void)
transition properly to the next song */
newpos = id3->filesize - id3->id3v1len - 1;
}
else if (newpos < (int)id3->id3v2len)
else if (newpos < (int)id3->first_frame_offset)
{
/* skip past id3v2 tag */
newpos = id3->id3v2len;
/* skip past id3v2 tag and other leading garbage */
newpos = id3->first_frame_offset;
}
if (mpeg_file >= 0)
@ -1720,7 +1721,7 @@ static void mpeg_thread(void)
t2 = current_tick;
DEBUGF("time: %d\n", t2 - t1);
DEBUGF("R: %x\n", len);
/* Now make sure that we don't feed the MAS with ID3V1
data */
if (len < amount_to_read)
@ -1734,19 +1735,19 @@ static void mpeg_thread(void)
{
if(tagptr >= mp3buflen)
tagptr -= mp3buflen;
if(mp3buf[tagptr] != tag[i])
taglen = 0;
tagptr++;
}
if(taglen)
{
/* Skip id3v1 tag */
DEBUGF("Skipping ID3v1 tag\n");
len -= taglen;
/* The very rare case when the entire tag
wasn't read in this read() call must be
taken care of */
@ -1819,131 +1820,135 @@ static void mpeg_thread(void)
}
else
{
/* This doesn't look neccessary...
yield();
if(!queue_empty(&mpeg_queue))
{*/
queue_wait(&mpeg_queue, &ev);
switch(ev.id)
{
case MPEG_RECORD:
DEBUGF("Recording...\n");
reset_mp3_buffer();
start_recording();
demand_irq_enable(true);
mpeg_file = creat(recording_filename, O_WRONLY);
queue_wait(&mpeg_queue, &ev);
switch(ev.id)
{
case MPEG_RECORD:
DEBUGF("Recording...\n");
reset_mp3_buffer();
if(mpeg_file < 0)
panicf("recfile: %d", mpeg_file);
/* Advance the write pointer 4096+417 bytes to make
room for an ID3 tag plus a VBR header */
mp3buf_write = 4096+417;
memset(mp3buf, 0, 4096+417);
start_recording();
demand_irq_enable(true);
mpeg_file = creat(recording_filename, O_WRONLY);
if(mpeg_file < 0)
panicf("recfile: %d", mpeg_file);
close(mpeg_file);
mpeg_file = -1;
break;
case MPEG_STOP:
DEBUGF("MPEG_STOP\n");
demand_irq_enable(false);
stop_recording();
/* Save the remaining data in the buffer */
stop_pending = true;
queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0);
break;
case MPEG_STOP_DONE:
DEBUGF("MPEG_STOP_DONE\n");
if(mpeg_file >= 0)
close(mpeg_file);
mpeg_file = -1;
break;
case MPEG_STOP:
DEBUGF("MPEG_STOP\n");
demand_irq_enable(false);
stop_recording();
/* Save the remaining data in the buffer */
stop_pending = true;
queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0);
break;
case MPEG_STOP_DONE:
DEBUGF("MPEG_STOP_DONE\n");
if(mpeg_file >= 0)
close(mpeg_file);
mpeg_file = -1;
mpeg_file = -1;
#ifdef DEBUG1
{
int i;
for(i = 0;i < 512;i++)
{
int i;
for(i = 0;i < 512;i++)
{
DEBUGF("%d - %d us (%d bytes)\n",
timing_info[i*2],
(timing_info[i*2+1] & 0xffff) *
10000 / 13824,
timing_info[i*2+1] >> 16);
}
DEBUGF("%d - %d us (%d bytes)\n",
timing_info[i*2],
(timing_info[i*2+1] & 0xffff) *
10000 / 13824,
timing_info[i*2+1] >> 16);
}
}
#endif
mpeg_stop_done = true;
break;
case MPEG_SAVE_DATA:
amount_to_save = mp3buf_write - mp3buf_read;
/* If the result is negative, the write index has
wrapped */
if(amount_to_save < 0)
mpeg_stop_done = true;
break;
case MPEG_SAVE_DATA:
amount_to_save = mp3buf_write - mp3buf_read;
/* If the result is negative, the write index has
wrapped */
if(amount_to_save < 0)
{
amount_to_save += mp3buflen;
}
DEBUGF("r: %x w: %x\n", mp3buf_read, mp3buf_write);
DEBUGF("ats: %x\n", amount_to_save);
/* Save data only if the buffer is getting full,
or if we should stop recording */
if(amount_to_save)
{
if(mp3buflen - amount_to_save < MPEG_LOW_WATER ||
stop_pending)
{
amount_to_save += mp3buflen;
}
DEBUGF("r: %x w: %x\n", mp3buf_read, mp3buf_write);
DEBUGF("ats: %x\n", amount_to_save);
/* Save data only if the buffer is getting full,
or if we should stop recording */
if(amount_to_save)
{
if(mp3buflen - amount_to_save < MPEG_LOW_WATER ||
stop_pending)
{
int rc;
/* Only save up to the end of the buffer */
writelen = MIN(amount_to_save,
mp3buflen - mp3buf_read);
DEBUGF("wrl: %x\n", writelen);
mpeg_file = open(recording_filename,
O_WRONLY| O_APPEND);
if(mpeg_file < 0)
panicf("rec open: %d", mpeg_file);
rc = write(mpeg_file, mp3buf + mp3buf_read,
writelen);
if(rc < 0)
panicf("rec wrt: %d", rc);
rc = close(mpeg_file);
if(rc < 0)
panicf("rec cls: %d", rc);
mpeg_file = -1;
DEBUGF("rc: %x\n", rc);
mp3buf_read += amount_to_save;
if(mp3buf_read >= mp3buflen)
mp3buf_read = 0;
queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0);
}
else
{
saving = false;
}
int rc;
/* Only save up to the end of the buffer */
writelen = MIN(amount_to_save,
mp3buflen - mp3buf_read);
DEBUGF("wrl: %x\n", writelen);
mpeg_file = open(recording_filename,
O_WRONLY| O_APPEND);
if(mpeg_file < 0)
panicf("rec open: %d", mpeg_file);
rc = write(mpeg_file, mp3buf + mp3buf_read,
writelen);
if(rc < 0)
panicf("rec wrt: %d", rc);
rc = close(mpeg_file);
if(rc < 0)
panicf("rec cls: %d", rc);
mpeg_file = -1;
DEBUGF("rc: %x\n", rc);
mp3buf_read += amount_to_save;
if(mp3buf_read >= mp3buflen)
mp3buf_read = 0;
queue_post(&mpeg_queue, MPEG_SAVE_DATA, 0);
}
else
{
/* We have saved all data,
time to stop for real */
if(stop_pending)
queue_post(&mpeg_queue, MPEG_STOP_DONE, 0);
saving = false;
}
break;
case MPEG_INIT_PLAYBACK:
init_playback();
init_playback_done = true;
break;
}
/*}*/
}
else
{
/* We have saved all data,
time to stop for real */
if(stop_pending)
queue_post(&mpeg_queue, MPEG_STOP_DONE, 0);
saving = false;
}
break;
case MPEG_INIT_PLAYBACK:
init_playback();
init_playback_done = true;
break;
}
}
#endif
}
@ -2981,3 +2986,74 @@ void mpeg_init(int volume, int bass, int treble, int balance, int loudness,
dbg_cnt2us(0);
#endif
}
int d_1;
int d_2;
int mpeg_create_xing_header(char *filename, void (*progressfunc)(int))
{
struct mp3entry entry;
char xingbuf[417];
int fd;
int rc;
int flen;
int num_frames;
int fpos;
if(progressfunc)
progressfunc(0);
rc = mp3info(&entry, filename);
if(rc < 0)
return rc * 10 - 1;
fd = open(filename, O_RDWR);
if(fd < 0)
return fd * 10 - 2;
flen = lseek(fd, 0, SEEK_END);
d_1 = entry.first_frame_offset;
d_2 = entry.filesize;
if(progressfunc)
progressfunc(0);
num_frames = count_mp3_frames(fd, entry.first_frame_offset,
flen,
progressfunc);
create_xing_header(fd, entry.first_frame_offset,
flen, xingbuf, num_frames, progressfunc);
/* Try to fit the Xing header first in the stream. Replace the existing
Xing header if there is one, else see if there is room between the
ID3 tag and the first MP3 frame. */
if(entry.xing_header_pos)
{
/* Reuse existing Xing header */
fpos = entry.xing_header_pos;
}
else
{
/* Any room between ID3 tag and first MP3 frame? */
if(entry.first_frame_offset - entry.id3v2len > 417)
{
fpos = entry.first_frame_offset - 417;
}
else
{
close(fd);
return -3;
}
}
lseek(fd, fpos, SEEK_SET);
write(fd, xingbuf, 417);
close(fd);
if(progressfunc)
progressfunc(100);
return 0;
}

View file

@ -89,7 +89,7 @@ ifeq ($(DISPLAY),-DHAVE_LCD_BITMAP)
else
LCDSRSC = lcd-playersim.c lcd-player.c lcd-player-charset.c font-player.c
endif
FIRMSRCS = $(LCDSRSC) id3.c usb.c mpeg.c powermgmt.c power.c $(EXTRAFIRMSRC)
FIRMSRCS = $(LCDSRSC) id3.c mp3data.c usb.c mpeg.c powermgmt.c power.c $(EXTRAFIRMSRC)
APPS = main.c tree.c menu.c credits.c main_menu.c icons.c language.c \
playlist.c wps.c wps-display.c settings.c status.c \
@ -234,6 +234,9 @@ $(OBJDIR)/settings.o: $(APPDIR)/settings.c
$(OBJDIR)/id3.o: $(FIRMWAREDIR)/id3.c
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/mp3data.o: $(FIRMWAREDIR)/mp3data.c
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR)/font.o: $(FIRMWAREDIR)/font.c
$(CC) $(CFLAGS) -c $< -o $@

View file

@ -93,7 +93,7 @@ else
LCDSRSC = lcd-playersim.c lcd-player.c font-player.c lcd-player-charset.c
endif
FIRMSRCS = $(LCDSRSC) id3.c debug.c usb.c mpeg.c power.c\
powermgmt.c panic.c
powermgmt.c panic.c mp3data.c
APPS = main.c tree.c menu.c credits.c main_menu.c language.c\
playlist.c wps.c wps-display.c settings.c status.c icons.c\
@ -275,6 +275,9 @@ $(OBJDIR)/peakmeter.o: $(RECDIR)/peakmeter.c
$(OBJDIR)/id3.o: $(FIRMWAREDIR)/id3.c
$(CC) $(APPCFLAGS) -c $< -o $@
$(OBJDIR)/mp3data.o: $(FIRMWAREDIR)/mp3data.c
$(CC) $(APPCFLAGS) -c $< -o $@
$(OBJDIR)/debug.o: $(FIRMWAREDIR)/debug.c
$(CC) $(CFLAGS) -c $< -o $@