This removes the need for an externally built library, thus officially removing the deprecated Xbox projects. Other minimal changes were made to ensure consistency. Comments will be made in the PR. Thanks to @Prcuvu for the initial push.
1171 lines
28 KiB
C++
1171 lines
28 KiB
C++
/* MAD is available from: http://www.underbit.com/products/mad/ */
|
|
|
|
#include "global.h"
|
|
#include "RageSoundReader_MP3.h"
|
|
#include "RageLog.h"
|
|
#include "RageUtil.h"
|
|
|
|
#include <cstdio>
|
|
#include <cerrno>
|
|
#include <map>
|
|
|
|
#include "mad.h"
|
|
|
|
// ID3 code from libid3:
|
|
enum tagtype {
|
|
TAGTYPE_NONE = 0,
|
|
TAGTYPE_ID3V1,
|
|
TAGTYPE_ID3V2,
|
|
TAGTYPE_ID3V2_FOOTER
|
|
};
|
|
|
|
static const int ID3_TAG_FLAG_FOOTERPRESENT = 0x10;
|
|
|
|
static tagtype tagtype( const unsigned char *data, id3_length_t length )
|
|
{
|
|
if (length >= 3 &&
|
|
data[0] == 'T' && data[1] == 'A' && data[2] == 'G')
|
|
return TAGTYPE_ID3V1;
|
|
|
|
if (length >= 10 &&
|
|
((data[0] == 'I' && data[1] == 'D' && data[2] == '3') ||
|
|
(data[0] == '3' && data[1] == 'D' && data[2] == 'I')) &&
|
|
data[3] < 0xff && data[4] < 0xff &&
|
|
data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80)
|
|
return data[0] == 'I' ? TAGTYPE_ID3V2 : TAGTYPE_ID3V2_FOOTER;
|
|
|
|
return TAGTYPE_NONE;
|
|
}
|
|
|
|
static unsigned long id3_parse_uint( const unsigned char **ptr, unsigned int bytes )
|
|
{
|
|
unsigned long value = 0;
|
|
|
|
ASSERT(bytes >= 1 && bytes <= 4);
|
|
|
|
switch (bytes)
|
|
{
|
|
case 4: value = (value << 8) | *(*ptr)++;
|
|
case 3: value = (value << 8) | *(*ptr)++;
|
|
case 2: value = (value << 8) | *(*ptr)++;
|
|
case 1: value = (value << 8) | *(*ptr)++;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static unsigned long id3_parse_syncsafe( const unsigned char **ptr, unsigned int bytes )
|
|
{
|
|
unsigned long value = 0;
|
|
|
|
ASSERT(bytes == 4 || bytes == 5);
|
|
|
|
switch (bytes)
|
|
{
|
|
case 5:
|
|
value = (value << 4) | (*(*ptr)++ & 0x0f);
|
|
case 4:
|
|
value = (value << 7) | (*(*ptr)++ & 0x7f);
|
|
value = (value << 7) | (*(*ptr)++ & 0x7f);
|
|
value = (value << 7) | (*(*ptr)++ & 0x7f);
|
|
value = (value << 7) | (*(*ptr)++ & 0x7f);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
static void parse_header(const unsigned char **ptr,
|
|
unsigned int *version, int *flags, id3_length_t *size)
|
|
{
|
|
*ptr += 3;
|
|
|
|
*version = id3_parse_uint(ptr, 2);
|
|
*flags = id3_parse_uint(ptr, 1);
|
|
*size = id3_parse_syncsafe(ptr, 4);
|
|
}
|
|
|
|
/*
|
|
* NAME: tag->query()
|
|
* DESCRIPTION: if a tag begins at the given location, return its size
|
|
*/
|
|
signed long id3_tag_query( const unsigned char *data, id3_length_t length )
|
|
{
|
|
unsigned int version;
|
|
int flags;
|
|
id3_length_t size;
|
|
|
|
switch (tagtype(data, length))
|
|
{
|
|
case TAGTYPE_ID3V1:
|
|
return 128;
|
|
|
|
case TAGTYPE_ID3V2:
|
|
parse_header(&data, &version, &flags, &size);
|
|
|
|
if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
|
|
size += 10;
|
|
|
|
return 10 + size;
|
|
|
|
case TAGTYPE_ID3V2_FOOTER:
|
|
parse_header(&data, &version, &flags, &size);
|
|
return -(int)size - 10;
|
|
|
|
case TAGTYPE_NONE:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* XING code ripped out of madplay (header)
|
|
*
|
|
* mad - MPEG audio decoder
|
|
* Copyright (C) 2000-2001 Robert Leslie
|
|
*/
|
|
struct xing
|
|
{
|
|
long flags; /* valid fields (see below) */
|
|
unsigned long frames; /* total number of frames */
|
|
unsigned long bytes; /* total number of bytes */
|
|
unsigned char toc[100]; /* 100-point seek table */
|
|
long scale; /* ?? */
|
|
enum { XING, INFO } type;
|
|
};
|
|
|
|
enum {
|
|
XING_FRAMES = 0x00000001L,
|
|
XING_BYTES = 0x00000002L,
|
|
XING_TOC = 0x00000004L,
|
|
XING_SCALE = 0x00000008L
|
|
};
|
|
|
|
void xing_init( struct xing *xing );
|
|
int xing_parse( struct xing *xing, struct mad_bitptr ptr, unsigned int bitlen );
|
|
|
|
/* end XING header */
|
|
|
|
|
|
/* a -= b */
|
|
static void mad_timer_sub(mad_timer_t *a, mad_timer_t b)
|
|
{
|
|
/* a = a - b -> a = a + -b */
|
|
mad_timer_negate(&b);
|
|
mad_timer_add(a, b);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* internal->decoder_private field */
|
|
struct madlib_t
|
|
{
|
|
madlib_t()
|
|
{
|
|
outpos = 0;
|
|
outleft = 0;
|
|
memset( inbuf, 0, sizeof(inbuf) );
|
|
memset( outbuf, 0, sizeof(outbuf) );
|
|
memset( &Stream, 0, sizeof(Stream) );
|
|
memset( &Frame, 0, sizeof(Frame) );
|
|
memset( &Synth, 0, sizeof(Synth) );
|
|
Timer = mad_timer_zero;
|
|
timer_accurate = false;
|
|
inbuf_filepos = 0;
|
|
filesize = 0;
|
|
header_bytes = 0;
|
|
first_frame = 0;
|
|
has_xing = 0;
|
|
memset( &xingtag, 0, sizeof(xingtag) );
|
|
length = 0;
|
|
framelength = mad_timer_zero;
|
|
bitrate = 0;
|
|
}
|
|
|
|
uint8_t inbuf[16384];
|
|
float outbuf[8192];
|
|
int outpos;
|
|
unsigned outleft;
|
|
|
|
struct mad_stream Stream;
|
|
struct mad_frame Frame;
|
|
struct mad_synth Synth;
|
|
/* Timestamp of the next frame. */
|
|
mad_timer_t Timer;
|
|
|
|
/* Whether Timer is trusted; this is false after doing a quick seek. */
|
|
int timer_accurate;
|
|
|
|
/*
|
|
* Frame index of each percentage of the file. This is constructed
|
|
* as we read the file, so we can quickly seek back later.
|
|
*/
|
|
struct mad_timer_compare_lt
|
|
{
|
|
bool operator() ( const mad_timer_t &timer1, const mad_timer_t &timer2 ) const
|
|
{
|
|
return mad_timer_compare( timer1, timer2 ) < 0;
|
|
}
|
|
};
|
|
|
|
typedef map<mad_timer_t, int, mad_timer_compare_lt> tocmap_t;
|
|
tocmap_t tocmap;
|
|
|
|
/* Position in the file of inbuf: */
|
|
int inbuf_filepos;
|
|
|
|
/* File size. */
|
|
int filesize;
|
|
|
|
/* Number of bytes of header data at the beginning; used for seeking. */
|
|
int header_bytes;
|
|
|
|
/* Whether we've decoded the first frame of real audio yet. If this
|
|
* is false, we're somewhere before the first frame; we might be in
|
|
* the middle of headers. */
|
|
int first_frame;
|
|
|
|
/* This data is filled in when the first frame is decoded. */
|
|
int has_xing; /* whether xingtag is valid */
|
|
struct xing xingtag;
|
|
|
|
/* If has_xing is true, this is filled in based on the xing header.
|
|
* If it's false, this is filled in based on the size of the file
|
|
* and the first frame. */
|
|
int length;
|
|
mad_timer_t framelength;
|
|
|
|
/* If we have a Xing tag, this is the average bitrate; otherwise it's
|
|
* the bitrate of the first frame. */
|
|
int bitrate;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static float scale(mad_fixed_t sample)
|
|
{
|
|
return float( double(sample) / (1<<MAD_F_FRACBITS) );
|
|
}
|
|
|
|
static int get_this_frame_byte( const madlib_t *mad )
|
|
{
|
|
int ret = mad->inbuf_filepos;
|
|
|
|
/* If we have a frame, adjust. */
|
|
if( mad->Stream.this_frame != NULL )
|
|
ret += mad->Stream.this_frame-mad->inbuf;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Called on the first frame decoded. Returns true if this frame
|
|
* should be ignored. */
|
|
bool RageSoundReader_MP3::handle_first_frame()
|
|
{
|
|
bool ret = false;
|
|
|
|
/* Check for a XING tag. */
|
|
xing_init( &mad->xingtag );
|
|
if( xing_parse(&mad->xingtag, mad->Stream.anc_ptr, mad->Stream.anc_bitlen) == 0 )
|
|
{
|
|
/*
|
|
* "Info" tags are written by some tools. They're just Xing tags, but for
|
|
* CBR files.
|
|
*
|
|
* However, DWI's decoder, BASS, doesn't understand this, and treats it as a
|
|
* corrupt frame, outputting a frame of silence. Let's ignore the tag, so
|
|
* it'll be treated as an invalid frame, so we match DWI sync.
|
|
*
|
|
* The information in it isn't very useful to us. The TOC is less accurate
|
|
* then computing it ourself (low resolution). The file length computation
|
|
* might be wrong, if the tag is incorrect, and we can compute that accurately
|
|
* ourself for CBR files.
|
|
*/
|
|
if( mad->xingtag.type == xing::INFO )
|
|
return false;
|
|
|
|
mad->header_bytes = max( mad->header_bytes, get_this_frame_byte(mad) );
|
|
|
|
mad->has_xing = true;
|
|
|
|
mad_timer_t tm = mad->Frame.header.duration;
|
|
/* XXX: does this include the Xing header itself? */
|
|
mad_timer_multiply( &tm, mad->xingtag.frames );
|
|
mad->length = mad_timer_count( tm, MAD_UNITS_MILLISECONDS );
|
|
|
|
/* XXX: an id3v2 footer tag would throw this off a little. This also assumes
|
|
* the Xing tag is the last header; it always is, I think. */
|
|
int bytes = mad->filesize - mad->header_bytes;
|
|
mad->bitrate = (int)(bytes * 8 / (mad->length/1000.f));
|
|
|
|
if( mad->xingtag.type == xing::XING )
|
|
ret = 1;
|
|
}
|
|
|
|
/* If there's no Xing tag, mad->length will be filled in by _open. */
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int RageSoundReader_MP3::fill_buffer()
|
|
{
|
|
/* Need more data. */
|
|
int inbytes = 0;
|
|
if( mad->Stream.next_frame != NULL )
|
|
{
|
|
/* Pull out remaining data from the last buffer. */
|
|
inbytes = mad->Stream.bufend-mad->Stream.next_frame;
|
|
memmove( mad->inbuf, mad->Stream.next_frame, inbytes );
|
|
mad->inbuf_filepos += mad->Stream.next_frame - mad->inbuf;
|
|
}
|
|
|
|
const bool bWasAtEOF = m_pFile->AtEOF();
|
|
|
|
int rc = m_pFile->Read( mad->inbuf + inbytes, sizeof(mad->inbuf)-inbytes-MAD_BUFFER_GUARD );
|
|
if( rc < 0 )
|
|
{
|
|
SetError( m_pFile->GetError() );
|
|
return -1;
|
|
}
|
|
|
|
if ( m_pFile->AtEOF() && !bWasAtEOF )
|
|
{
|
|
/* We just reached EOF. Append MAD_BUFFER_GUARD bytes of NULs to the
|
|
* buffer, to ensure that the last frame is flushed. */
|
|
memset( mad->inbuf + inbytes + rc, 0, MAD_BUFFER_GUARD );
|
|
rc += MAD_BUFFER_GUARD;
|
|
}
|
|
|
|
if ( rc == 0 )
|
|
return 0;
|
|
|
|
inbytes += rc;
|
|
mad_stream_buffer( &mad->Stream, mad->inbuf, inbytes );
|
|
return rc;
|
|
}
|
|
|
|
void fill_frame_index_cache( madlib_t *mad )
|
|
{
|
|
int pos;
|
|
|
|
/* Only update the frame cache if our timer is consistent. */
|
|
if(!mad->timer_accurate) return;
|
|
|
|
/* the frame we just decoded: */
|
|
pos = get_this_frame_byte(mad);
|
|
|
|
/* Fill in the TOC percent. */
|
|
if( !mad->tocmap.empty() )
|
|
{
|
|
/* Don't add an entry if one already exists near this time. */
|
|
madlib_t::tocmap_t::iterator it = mad->tocmap.upper_bound( mad->Timer );
|
|
if( it != mad->tocmap.begin() )
|
|
{
|
|
--it;
|
|
int iDiffSeconds = mad->Timer.seconds - it->first.seconds;
|
|
if( iDiffSeconds < 5 )
|
|
return;
|
|
}
|
|
}
|
|
|
|
mad->tocmap[mad->Timer] = pos;
|
|
}
|
|
|
|
/* Handle first-stage decoding: extracting the MP3 frame data. */
|
|
int RageSoundReader_MP3::do_mad_frame_decode( bool headers_only )
|
|
{
|
|
int bytes_read = 0;
|
|
|
|
while(1)
|
|
{
|
|
int ret;
|
|
|
|
/* Always actually decode the first packet, so we cleanly parse Xing tags. */
|
|
if( headers_only && !mad->first_frame )
|
|
ret=mad_header_decode( &mad->Frame.header,&mad->Stream );
|
|
else
|
|
ret=mad_frame_decode( &mad->Frame,&mad->Stream );
|
|
|
|
if( ret == -1 && (mad->Stream.error == MAD_ERROR_BUFLEN || mad->Stream.error == MAD_ERROR_BUFPTR) )
|
|
{
|
|
if( bytes_read > 25000 )
|
|
{
|
|
/* We've read this much without actually getting a frame; error. */
|
|
SetError( "Can't find data" );
|
|
return -1;
|
|
}
|
|
|
|
ret = fill_buffer();
|
|
if( ret <= 0 )
|
|
return ret;
|
|
bytes_read += ret;
|
|
|
|
continue;
|
|
}
|
|
|
|
if( ret == -1 && mad->Stream.error == MAD_ERROR_LOSTSYNC )
|
|
{
|
|
/* This might be an ID3V2 tag. */
|
|
const int tagsize = id3_tag_query(mad->Stream.this_frame,
|
|
mad->Stream.bufend - mad->Stream.this_frame);
|
|
|
|
if( tagsize )
|
|
{
|
|
mad_stream_skip(&mad->Stream, tagsize);
|
|
|
|
/* Don't count the tagsize against the max-read-per-call figure. */
|
|
bytes_read -= tagsize;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( ret == -1 && mad->Stream.error == MAD_ERROR_BADDATAPTR )
|
|
{
|
|
/*
|
|
* Something's corrupt. One cause of this is cutting an MP3 in the middle
|
|
* without reencoding; the first two frames will reference data from previous
|
|
* frames that have been removed. The frame is valid--we can get a header from
|
|
* it, we just can't synth useful data.
|
|
*
|
|
* BASS pretends the bad frames are silent. Emulate that, for compatibility.
|
|
*/
|
|
ret = 0; /* pretend success */
|
|
}
|
|
|
|
if( !ret )
|
|
{
|
|
/* OK. */
|
|
if( mad->first_frame )
|
|
{
|
|
/* We're at the beginning. Is this a Xing tag? */
|
|
if(handle_first_frame())
|
|
{
|
|
/* The first frame contained a header. Continue searching. */
|
|
continue;
|
|
}
|
|
|
|
/* We've decoded the first frame of data.
|
|
*
|
|
* We want mad->Timer to represent the timestamp of the first sample of the
|
|
* currently decoded frame. Don't increment mad->Timer on the first frame,
|
|
* or it'll be the time of the *next* frame. (All frames have the same
|
|
* duration.) */
|
|
mad->first_frame = false;
|
|
mad->Timer = mad_timer_zero;
|
|
mad->header_bytes = get_this_frame_byte(mad);
|
|
}
|
|
else
|
|
{
|
|
mad_timer_add( &mad->Timer,mad->Frame.header.duration );
|
|
}
|
|
|
|
fill_frame_index_cache( mad );
|
|
|
|
return 1;
|
|
}
|
|
|
|
if( mad->Stream.error == MAD_ERROR_BADCRC )
|
|
{
|
|
/* XXX untested */
|
|
mad_frame_mute(&mad->Frame);
|
|
mad_synth_mute(&mad->Synth);
|
|
|
|
continue;
|
|
}
|
|
|
|
if( !MAD_RECOVERABLE(mad->Stream.error) )
|
|
{
|
|
/* We've received an unrecoverable error. */
|
|
SetError( mad_stream_errorstr(&mad->Stream) );
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void RageSoundReader_MP3::synth_output()
|
|
{
|
|
mad->outleft = mad->outpos = 0;
|
|
|
|
if( MAD_NCHANNELS(&mad->Frame.header) != this->Channels )
|
|
{
|
|
/* This frame contains a different number of channels than the first.
|
|
* I've never actually seen this--it's just to prevent exploding if
|
|
* it happens--and we could continue on easily enough, so if it happens,
|
|
* just continue. */
|
|
return;
|
|
}
|
|
|
|
mad_synth_frame(&mad->Synth, &mad->Frame);
|
|
for(int i=0; i < mad->Synth.pcm.length; i++)
|
|
{
|
|
for(int chan = 0; chan < this->Channels; ++chan)
|
|
{
|
|
float Sample = scale(mad->Synth.pcm.samples[chan][i]);
|
|
mad->outbuf[mad->outleft] = Sample;
|
|
++mad->outleft;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Seek to a byte in the file. If you're going to put the file position
|
|
* back when you're done, and not going to read any data, you don't have
|
|
* to use this. */
|
|
int RageSoundReader_MP3::seek_stream_to_byte( int byte )
|
|
{
|
|
if( m_pFile->Seek(byte) == -1 )
|
|
{
|
|
SetError( strerror(errno) );
|
|
return 0;
|
|
}
|
|
|
|
mad_frame_mute(&mad->Frame);
|
|
mad_synth_mute(&mad->Synth);
|
|
mad_stream_finish(&mad->Stream);
|
|
mad_stream_init(&mad->Stream);
|
|
|
|
mad->outleft = mad->outpos = 0;
|
|
|
|
mad->inbuf_filepos = byte;
|
|
|
|
/* If the position is <= the position of the first audio sample, then
|
|
* we're at the beginning. */
|
|
if( !mad->tocmap.empty() )
|
|
mad->first_frame = ( byte <= mad->tocmap.begin()->second );
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
#if 0
|
|
/* Call this after seeking the stream. We'll back up a bit and reread
|
|
* frames until we're back where we started, so the next read is aligned
|
|
* to a frame and synced. This must never leave the position ahead of where
|
|
* it way, since that can confuse the seek optimizations. */
|
|
int RageSoundReader_MP3::resync()
|
|
{
|
|
/* Save the timer; decoding will change it, and we need to put it back. */
|
|
mad_timer_t orig = mad->Timer;
|
|
|
|
/* Seek backwards up to 4k. */
|
|
const int origpos = mad->inbuf_filepos;
|
|
const int seekpos = max( 0, origpos - 1024*4 );
|
|
seek_stream_to_byte( seekpos );
|
|
|
|
/* Agh. This is annoying. We want to decode enough so that the next frame
|
|
* read will be the first frame after the current file pointer. If we just
|
|
* read until the file pointer is >= what it was, we've passed it already.
|
|
* So, read until it's >= what it was, counting the number of times we had
|
|
* to read; then back up again and read n-1 times. Gross. */
|
|
int reads = 0;
|
|
do
|
|
{
|
|
if( do_mad_frame_decode() <= 0 ) /* XXX eof */
|
|
return -1; /* it set the error */
|
|
|
|
reads++;
|
|
} while( get_this_frame_byte(mad) < origpos );
|
|
|
|
seek_stream_to_byte( seekpos );
|
|
|
|
reads--;
|
|
while( reads-- > 0 )
|
|
{
|
|
if( do_mad_frame_decode() <= 0 ) /* XXX eof */
|
|
return -1; /* it set the error */
|
|
}
|
|
|
|
/* Restore the timer. */
|
|
mad->Timer = orig;
|
|
mad->outpos = mad->outleft = 0;
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
RageSoundReader_MP3::RageSoundReader_MP3()
|
|
{
|
|
mad = new madlib_t;
|
|
m_bAccurateSync = false;
|
|
|
|
mad_stream_init( &mad->Stream );
|
|
mad_frame_init( &mad->Frame );
|
|
mad_synth_init( &mad->Synth );
|
|
|
|
mad_frame_mute( &mad->Frame );
|
|
mad_timer_reset( &mad->Timer );
|
|
mad->length = -1;
|
|
mad->inbuf_filepos = 0;
|
|
mad->header_bytes = 0;
|
|
mad->has_xing = false;
|
|
mad->timer_accurate = 1;
|
|
mad->bitrate = -1;
|
|
mad->first_frame = true;
|
|
}
|
|
|
|
RageSoundReader_MP3::~RageSoundReader_MP3()
|
|
{
|
|
mad_synth_finish( &mad->Synth );
|
|
mad_frame_finish( &mad->Frame );
|
|
mad_stream_finish( &mad->Stream );
|
|
|
|
delete mad;
|
|
}
|
|
|
|
RageSoundReader_FileReader::OpenResult RageSoundReader_MP3::Open( RageFileBasic *pFile )
|
|
{
|
|
m_pFile = pFile;
|
|
|
|
mad->filesize = m_pFile->GetFileSize();
|
|
ASSERT( mad->filesize != -1 );
|
|
|
|
/* Make sure we can decode at least one frame. This will also fill in header info. */
|
|
mad->outpos = 0;
|
|
|
|
int ret = do_mad_frame_decode();
|
|
switch(ret)
|
|
{
|
|
case 0:
|
|
SetError( "Failed to read any data at all" );
|
|
return OPEN_UNKNOWN_FILE_FORMAT;
|
|
case -1:
|
|
SetError( GetError() + " (not an MP3 stream?)" );
|
|
return OPEN_UNKNOWN_FILE_FORMAT;
|
|
}
|
|
|
|
/* Store the bitrate of the frame we just got. */
|
|
if( mad->bitrate == -1 ) /* might have been set by a Xing tag */
|
|
mad->bitrate = mad->Frame.header.bitrate;
|
|
|
|
SampleRate = mad->Frame.header.samplerate;
|
|
mad->framelength = mad->Frame.header.duration;
|
|
this->Channels = MAD_NCHANNELS( &mad->Frame.header );
|
|
|
|
/* Since we've already decoded a frame, just synth it instead of rewinding
|
|
* the stream. */
|
|
synth_output();
|
|
|
|
if(mad->length == -1)
|
|
{
|
|
/* If vbr and !xing, this is just an estimate. */
|
|
int bps = mad->bitrate / 8;
|
|
float secs = (float)(mad->filesize - mad->header_bytes) / bps;
|
|
mad->length = (int)(secs * 1000.f);
|
|
}
|
|
|
|
return OPEN_OK;
|
|
}
|
|
|
|
|
|
RageSoundReader_MP3 *RageSoundReader_MP3::Copy() const
|
|
{
|
|
RageSoundReader_MP3 *ret = new RageSoundReader_MP3;
|
|
|
|
ret->m_pFile = m_pFile->Copy();
|
|
ret->m_pFile->Seek( 0 );
|
|
ret->m_bAccurateSync = m_bAccurateSync;
|
|
ret->mad->filesize = mad->filesize;
|
|
ret->mad->bitrate = mad->bitrate;
|
|
ret->SampleRate = SampleRate;
|
|
ret->mad->framelength = mad->framelength;
|
|
ret->Channels = Channels;
|
|
ret->mad->length = mad->length;
|
|
|
|
// int n = ret->do_mad_frame_decode();
|
|
// ASSERT( n > 0 );
|
|
// ret->synth_output();
|
|
|
|
return ret;
|
|
}
|
|
|
|
int RageSoundReader_MP3::Read( float *buf, int iFrames )
|
|
{
|
|
int iFramesWritten = 0;
|
|
|
|
while( iFrames > 0 )
|
|
{
|
|
if( mad->outleft > 0 )
|
|
{
|
|
int iFramesToCopy = min( iFrames, int(mad->outleft / GetNumChannels()) );
|
|
const int iSamplesToCopy = iFramesToCopy * GetNumChannels();
|
|
const int iBytesToCopy = iSamplesToCopy * sizeof(float);
|
|
|
|
memcpy( buf, mad->outbuf + mad->outpos, iBytesToCopy );
|
|
|
|
buf += iSamplesToCopy;
|
|
iFrames -= iFramesToCopy;
|
|
iFramesWritten += iFramesToCopy;
|
|
mad->outpos += iSamplesToCopy;
|
|
mad->outleft -= iSamplesToCopy;
|
|
continue;
|
|
}
|
|
|
|
/* Decode more from the MP3 stream. */
|
|
int ret = do_mad_frame_decode();
|
|
if( ret == 0 )
|
|
return END_OF_FILE;
|
|
if( ret == -1 )
|
|
return ERROR;
|
|
|
|
synth_output();
|
|
}
|
|
|
|
return iFramesWritten;
|
|
}
|
|
|
|
bool RageSoundReader_MP3::MADLIB_rewind()
|
|
{
|
|
m_pFile->Seek(0);
|
|
|
|
mad_frame_mute(&mad->Frame);
|
|
mad_synth_mute(&mad->Synth);
|
|
mad_timer_reset(&mad->Timer);
|
|
mad->outpos = mad->outleft = 0;
|
|
|
|
mad_stream_finish(&mad->Stream);
|
|
mad_stream_init(&mad->Stream);
|
|
mad_stream_buffer(&mad->Stream, NULL, 0);
|
|
mad->inbuf_filepos = 0;
|
|
|
|
/* Be careful. We need to leave header_bytes alone, so if we try to SetPosition_estimate
|
|
* immediately after this, we still know the header size. However, we need to set first_frame
|
|
* to true, since the first frame is handled specially in do_mad_frame_decode; if we don't
|
|
* set it, then we'll be desynced by a frame after an accurate seek. */
|
|
// mad->header_bytes = 0;
|
|
mad->first_frame = true;
|
|
mad->Stream.this_frame = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Methods of seeking:
|
|
*
|
|
* 1. We can jump based on a TOC. We potentially have two; the Xing TOC and our
|
|
* own index. The Xing TOC is only accurate to 1/256th of the file size,
|
|
* so it's unsuitable for precise seeks. Our own TOC is byte-accurate.
|
|
* (SetPosition_toc)
|
|
*
|
|
* 2. We can jump based on the bitrate. This is fast, but not accurate.
|
|
* (SetPosition_estimate)
|
|
*
|
|
* 3. We can seek from any position to any higher position by decoding headers.
|
|
* (SetPosition_hard)
|
|
*
|
|
* Both 1 and 2 will leave the position behind the actual requested position;
|
|
* combine them with 3 to catch up. Never do 3 alone in "fast" mode, since it's
|
|
* slow if it ends up seeking from the beginning of the file. Never do 2 in
|
|
* "precise" mode.
|
|
*/
|
|
|
|
/* Returns actual position on success, 0 if past EOF, -1 on error. */
|
|
int RageSoundReader_MP3::SetPosition_toc( int iFrame, bool Xing )
|
|
{
|
|
ASSERT( !Xing || mad->has_xing );
|
|
ASSERT( mad->length != -1 );
|
|
|
|
/* This leaves our timer accurate if we're using our own TOC, and inaccurate
|
|
* if we're using Xing. */
|
|
mad->timer_accurate = !Xing;
|
|
|
|
int bytepos = -1;
|
|
if( Xing )
|
|
{
|
|
/* We can speed up the seek using the XING tag. First, figure
|
|
* out what percentage the requested position falls in. */
|
|
ASSERT( SampleRate != 0 );
|
|
int ms = int( (iFrame * 1000LL) / SampleRate );
|
|
ASSERT( mad->length != 0 );
|
|
int percent = ms * 100 / mad->length;
|
|
if( percent < 100 )
|
|
{
|
|
int jump = mad->xingtag.toc[percent];
|
|
bytepos = mad->filesize * jump / 256;
|
|
}
|
|
else
|
|
bytepos = 2000000000; /* force EOF */
|
|
|
|
mad_timer_set( &mad->Timer, 0, percent * mad->length, 100000 );
|
|
}
|
|
else
|
|
{
|
|
mad_timer_t desired;
|
|
mad_timer_set( &desired, 0, iFrame, SampleRate );
|
|
|
|
if( mad->tocmap.empty() )
|
|
return 1; /* don't have any info */
|
|
|
|
/* Find the last entry <= iFrame that we actually have an entry for;
|
|
* this will get us as close as possible. */
|
|
madlib_t::tocmap_t::iterator it = mad->tocmap.upper_bound( desired );
|
|
if( it == mad->tocmap.begin() )
|
|
return 1; /* don't have any info */
|
|
--it;
|
|
|
|
mad->Timer = it->first;
|
|
bytepos = it->second;
|
|
}
|
|
|
|
if( bytepos != -1 )
|
|
{
|
|
/* Seek backwards up to 4k. */
|
|
const int seekpos = max( 0, bytepos - 1024*4 );
|
|
seek_stream_to_byte( seekpos );
|
|
|
|
do
|
|
{
|
|
int ret = do_mad_frame_decode();
|
|
if( ret <= 0 )
|
|
return ret; /* it set the error */
|
|
} while( get_this_frame_byte(mad) < bytepos );
|
|
synth_output();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int RageSoundReader_MP3::SetPosition_hard( int iFrame )
|
|
{
|
|
mad_timer_t desired;
|
|
mad_timer_set( &desired, 0, iFrame, mad->Frame.header.samplerate );
|
|
|
|
/* This seek doesn't change the accuracy of our timer. */
|
|
|
|
/* If we're already exactly at the requested position, OK. */
|
|
if( mad_timer_compare(mad->Timer, desired) == 0 )
|
|
return 1;
|
|
|
|
/* We always come in here with data synthed. Be careful not to synth the
|
|
* same frame twice. */
|
|
bool synthed=true;
|
|
|
|
/* If we're already past the requested position, rewind. */
|
|
if(mad_timer_compare(mad->Timer, desired) > 0)
|
|
{
|
|
MADLIB_rewind();
|
|
do_mad_frame_decode();
|
|
synthed = false;
|
|
}
|
|
|
|
/* Decode frames until the current frame contains the desired offset. */
|
|
while(1)
|
|
{
|
|
/* If desired < next_frame_timer, this frame contains the position. Since we've
|
|
* already decoded the frame, synth it, too. */
|
|
mad_timer_t next_frame_timer = mad->Timer;
|
|
mad_timer_add( &next_frame_timer, mad->framelength );
|
|
|
|
if( mad_timer_compare(desired, next_frame_timer) < 0 )
|
|
{
|
|
if( !synthed )
|
|
{
|
|
synth_output();
|
|
}
|
|
else
|
|
{
|
|
mad->outleft += mad->outpos;
|
|
mad->outpos = 0;
|
|
}
|
|
|
|
/* We just synthed data starting at mad->Timer, containing the desired offset.
|
|
* Skip (desired - mad->Timer) worth of frames in the output to line up. */
|
|
mad_timer_t skip = desired;
|
|
mad_timer_sub( &skip, mad->Timer );
|
|
|
|
int samples = mad_timer_count( skip, (mad_units) SampleRate );
|
|
|
|
/* Skip 'samples' samples. */
|
|
mad->outpos = samples * this->Channels;
|
|
mad->outleft -= samples * this->Channels;
|
|
return 1;
|
|
}
|
|
|
|
/* Otherwise, if the desired time will be in the *next* decode, then synth
|
|
* this one, too. */
|
|
mad_timer_t next_next_frame_timer = next_frame_timer;
|
|
mad_timer_add( &next_next_frame_timer, mad->framelength );
|
|
|
|
if( mad_timer_compare(desired, next_next_frame_timer) < 0 && !synthed )
|
|
{
|
|
synth_output();
|
|
synthed = true;
|
|
}
|
|
|
|
int ret = do_mad_frame_decode();
|
|
if( ret <= 0 )
|
|
{
|
|
mad->outleft = mad->outpos = 0;
|
|
return ret; /* it set the error */
|
|
}
|
|
|
|
synthed = false;
|
|
}
|
|
}
|
|
|
|
/* Do a seek based on the bitrate. */
|
|
int RageSoundReader_MP3::SetPosition_estimate( int iFrame )
|
|
{
|
|
/* This doesn't leave us accurate. */
|
|
mad->timer_accurate = 0;
|
|
|
|
mad_timer_t seekamt;
|
|
mad_timer_set( &seekamt, 0, iFrame, mad->Frame.header.samplerate );
|
|
{
|
|
/* We're going to skip ahead two samples below, so seek earlier than
|
|
* we were asked to. */
|
|
mad_timer_t back_len = mad->framelength;
|
|
mad_timer_multiply(&back_len, -2);
|
|
mad_timer_add(&seekamt, back_len);
|
|
if( mad_timer_compare(seekamt, mad_timer_zero) < 0 )
|
|
seekamt = mad_timer_zero;
|
|
}
|
|
|
|
int seekpos = mad_timer_count( seekamt, MAD_UNITS_MILLISECONDS ) * (mad->bitrate / 8 / 1000);
|
|
seekpos += mad->header_bytes;
|
|
seek_stream_to_byte( seekpos );
|
|
|
|
/* We've jumped across the file, so the decoder is currently desynced.
|
|
* Don't use resync(); it's slow. Just decode a few frames. */
|
|
for( int i = 0; i < 2; ++i )
|
|
{
|
|
int ret = do_mad_frame_decode();
|
|
if( ret <= 0 )
|
|
return ret;
|
|
}
|
|
|
|
/* Throw out one synth. */
|
|
synth_output();
|
|
mad->outleft = 0;
|
|
|
|
/* Find out where we really seeked to. */
|
|
int ms = (get_this_frame_byte(mad) - mad->header_bytes) / (mad->bitrate / 8 / 1000);
|
|
mad_timer_set(&mad->Timer, 0, ms, 1000);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int RageSoundReader_MP3::SetPosition( int iFrame )
|
|
{
|
|
if( m_bAccurateSync )
|
|
{
|
|
/* Seek using our own internal (accurate) TOC. */
|
|
int ret = SetPosition_toc( iFrame, false );
|
|
if( ret <= 0 )
|
|
return ret; /* it set the error */
|
|
|
|
/* Align exactly. */
|
|
return SetPosition_hard( iFrame );
|
|
}
|
|
else
|
|
{
|
|
/* Rewinding is always fast and accurate, and SetPosition_estimate is bad at 0. */
|
|
if( !iFrame )
|
|
{
|
|
MADLIB_rewind();
|
|
return 1; /* ok */
|
|
}
|
|
|
|
/* We can do a fast jump in VBR with Xing with more accuracy than without Xing. */
|
|
if( mad->has_xing )
|
|
return SetPosition_toc( iFrame, true );
|
|
|
|
/* Guess. This is only remotely accurate when we're not VBR, but also
|
|
* do it if we have no Xing tag. */
|
|
return SetPosition_estimate( iFrame );
|
|
}
|
|
}
|
|
|
|
bool RageSoundReader_MP3::SetProperty( const RString &sProperty, float fValue )
|
|
{
|
|
if( sProperty == "AccurateSync" )
|
|
{
|
|
m_bAccurateSync = (fValue > 0.001f);
|
|
return true;
|
|
}
|
|
|
|
return RageSoundReader_FileReader::SetProperty( sProperty, fValue );
|
|
}
|
|
|
|
int RageSoundReader_MP3::GetNextSourceFrame() const
|
|
{
|
|
int iFrame = mad_timer_count( mad->Timer, mad_units(mad->Frame.header.samplerate) );
|
|
iFrame += mad->outpos / this->Channels;
|
|
return iFrame;
|
|
}
|
|
|
|
int RageSoundReader_MP3::GetLengthInternal( bool fast )
|
|
{
|
|
if( mad->has_xing && mad->length != -1 )
|
|
return mad->length; /* should be accurate */
|
|
|
|
/* Check to see if a frame in the middle of the file is the same
|
|
* bitrate as the first frame. If it is, assume the file is really CBR. */
|
|
seek_stream_to_byte( mad->filesize / 2 );
|
|
|
|
/* XXX use mad_header_decode and check more than one frame */
|
|
if(mad->length != -1 &&
|
|
do_mad_frame_decode() &&
|
|
mad->bitrate == (int) mad->Frame.header.bitrate)
|
|
{
|
|
return mad->length;
|
|
}
|
|
|
|
if( !MADLIB_rewind() )
|
|
return 0;
|
|
|
|
/* Worst-case: vbr && !xing. We've made a guess at the length, but let's actually
|
|
* scan the size, since the guess is probably wrong. */
|
|
if( fast )
|
|
{
|
|
SetError("Can't estimate file length");
|
|
return -1;
|
|
}
|
|
|
|
MADLIB_rewind();
|
|
while(1)
|
|
{
|
|
int ret = do_mad_frame_decode( true );
|
|
if( ret == -1 )
|
|
return -1; /* it set the error */
|
|
if( ret == 0 ) /* EOF */
|
|
break;
|
|
}
|
|
|
|
/* mad->Timer is the timestamp of the current frame; find the timestamp of
|
|
* the very end. */
|
|
mad_timer_t end = mad->Timer;
|
|
mad_timer_add( &end, mad->framelength );
|
|
|
|
/* Count milliseconds. */
|
|
return mad_timer_count( end, MAD_UNITS_MILLISECONDS );
|
|
}
|
|
|
|
int RageSoundReader_MP3::GetLengthConst( bool fast ) const
|
|
{
|
|
RageSoundReader_MP3 *pCopy = this->Copy();
|
|
|
|
int iLength = pCopy->GetLengthInternal( fast );
|
|
|
|
delete pCopy;
|
|
return iLength;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* begin XING code ripped out of madplay */
|
|
|
|
/*
|
|
* NAME: xing->init()
|
|
* DESCRIPTION: initialize Xing structure
|
|
*/
|
|
void xing_init(struct xing *xing)
|
|
{
|
|
xing->flags = 0;
|
|
}
|
|
|
|
/*
|
|
* NAME: xing->parse()
|
|
* DESCRIPTION: parse a Xing VBR header
|
|
*/
|
|
int xing_parse(struct xing *xing, struct mad_bitptr ptr, unsigned int bitlen)
|
|
{
|
|
const unsigned XING_MAGIC = (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g');
|
|
const unsigned INFO_MAGIC = (('I' << 24) | ('n' << 16) | ('f' << 8) | 'o');
|
|
unsigned data;
|
|
if (bitlen < 64)
|
|
goto fail;
|
|
data = mad_bit_read(&ptr, 32);
|
|
|
|
if( data == XING_MAGIC )
|
|
xing->type = xing::XING;
|
|
else if( data == INFO_MAGIC )
|
|
xing->type = xing::INFO;
|
|
else
|
|
goto fail;
|
|
|
|
xing->flags = mad_bit_read(&ptr, 32);
|
|
bitlen -= 64;
|
|
|
|
if (xing->flags & XING_FRAMES)
|
|
{
|
|
if (bitlen < 32)
|
|
goto fail;
|
|
|
|
xing->frames = mad_bit_read(&ptr, 32);
|
|
bitlen -= 32;
|
|
}
|
|
|
|
if (xing->flags & XING_BYTES)
|
|
{
|
|
if (bitlen < 32)
|
|
goto fail;
|
|
|
|
xing->bytes = mad_bit_read(&ptr, 32);
|
|
bitlen -= 32;
|
|
}
|
|
|
|
if (xing->flags & XING_TOC)
|
|
{
|
|
if (bitlen < 800)
|
|
goto fail;
|
|
|
|
for (int i = 0; i < 100; ++i)
|
|
xing->toc[i] = (unsigned char) mad_bit_read(&ptr, 8);
|
|
|
|
bitlen -= 800;
|
|
}
|
|
|
|
if (xing->flags & XING_SCALE)
|
|
{
|
|
if (bitlen < 32)
|
|
goto fail;
|
|
|
|
xing->scale = mad_bit_read(&ptr, 32);
|
|
bitlen -= 32;
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
xing->flags = 0;
|
|
return -1;
|
|
}
|
|
|
|
/* end XING code ripped out of madplay */
|
|
|
|
/*
|
|
* Copyright (c) 2003-2004 Glenn Maynard
|
|
* Copyright (c) 2000-2003 Robert Leslie (Xing code from madplay)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|