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:
parent
22cbe938fe
commit
a039091187
8 changed files with 1016 additions and 425 deletions
|
@ -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
66
firmware/export/mp3data.h
Normal 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
|
|
@ -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
|
||||
|
|
371
firmware/id3.c
371
firmware/id3.c
|
@ -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
664
firmware/mp3data.c
Normal 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;
|
||||
}
|
320
firmware/mpeg.c
320
firmware/mpeg.c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 $@
|
||||
|
||||
|
|
|
@ -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 $@
|
||||
|
||||
|
|
Loading…
Reference in a new issue