jedi-academy/code/mp3code/towave.c
2013-04-23 15:21:39 +10:00

766 lines
22 KiB
C

/*____________________________________________________________________________
FreeAmp - The Free MP3 Player
MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology
Corp. http://www.xingtech.com
Portions Copyright (C) 1998-1999 EMusic.com
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., 675 Mass Ave, Cambridge, MA 02139, USA.
$Id: towave.c,v 1.3 1999/10/19 07:13:09 elrod Exp $
____________________________________________________________________________*/
/* ------------------------------------------------------------------------
NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
This file exists for reference only. It is not actually used
in the FreeAmp project. There is no need to mess with this
file. There is no need to flatten the beavers, either.
NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
/*---- towave.c --------------------------------------------
32 bit version only
decode mpeg Layer I/II/III file using portable ANSI C decoder,
output to pcm wave file.
mod 8/19/98 decode 22 sf bands
mod 5/14/98 allow mpeg25 (dec8 not supported for mpeg25 samp rate)
mod 3/4/98 bs_trigger bs_bufbytes made signed, unsigned may
not terminate properly. Also extra test in bs_fill.
mod 8/6/96 add 8 bit output to standard decoder
ver 1.4 mods 7/18/96 32 bit and add asm option
mods 6/29/95 allow MS wave file for u-law. bugfix u-law table dec8.c
mods 2/95 add sample rate reduction, freq_limit and conversions.
add _decode8 for 8Ks output, 16bit 8bit, u-law output.
add additional control parameters to init.
add _info function
mod 5/12/95 add quick window cwinq.c
mod 5/19/95 change from stream io to handle io
mod 11/16/95 add Layer I
mod 1/5/95 integer overflow mod iup.c
ver 1.3
mod 2/5/96 portability mods
drop Tom and Gloria pcm file types
ver 2.0
mod 1/7/97 Layer 3 (float mpeg-1 only)
2/6/97 Layer 3 MPEG-2
ver 3.01 Layer III bugfix crc problem 8/18/97
ver 3.02 Layer III fix wannabe.mp3 problem 10/9/97
ver 3.03 allow mpeg 2.5 5/14/98
Decoder functions for _decode8 are defined in dec8.c. Useage
is same as regular decoder.
Towave illustrates use of decoder. Towave converts
mpeg audio files to 16 bit (short) pcm. Default pcm file
format is wave. Other formats can be accommodated by
adding alternative write_pcm_header and write_pcm_tailer
functions. The functions kbhit and getch used in towave.c
may not port to other systems.
The decoder handles all mpeg1 and mpeg2 Layer I/II bitstreams.
For compatability with the asm decoder and future C versions,
source code users are discouraged from making modifications
to the decoder proper. MS Windows applications can use wrapper
functions in a separate module if decoder functions need to
be exported.
NOTE additional control parameters.
mod 8/6/96 standard decoder adds 8 bit output
decode8 (8Ks output) convert_code:
convert_code = 4*bit_code + chan_code
bit_code: 1 = 16 bit linear pcm
2 = 8 bit (unsigned) linear pcm
3 = u-law (8 bits unsigned)
chan_code: 0 = convert two chan to mono
1 = convert two chan to mono
2 = convert two chan to left chan
3 = convert two chan to right chan
decode (standard decoder) convert_code:
0 = two chan output
1 = convert two chan to mono
2 = convert two chan to left chan
3 = convert two chan to right chan
or with 8 = 8 bit output
(other bits ignored)
decode (standard decoder) reduction_code:
0 = full sample rate output
1 = half rate
2 = quarter rate
-----------------------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include <float.h>
#include <math.h>
#include <string.h>
#ifdef WIN32
#include <io.h>
#endif
#include <fcntl.h> /* file open flags */
#include <sys/types.h> /* someone wants for port */
#include <sys/stat.h> /* forward slash for portability */
#include "mhead.h" /* mpeg header structure, decode protos */
#include "port.h"
// JDW
#ifdef __linux__
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <fcntl.h>
#include <errno.h>
#endif
// JDW
#include "mp3struct.h"
#include <assert.h>
#ifndef byte
typedef unsigned char byte;
#endif
typedef struct id3v1_1 {
char id[3];
char title[30]; // <file basename>
char artist[30]; // "Raven Software"
char album[30]; // "#UNCOMP %d" // needed
char year[4]; // "2000"
char comment[28]; // "#MAXVOL %g" // needed
char zero;
char track;
char genre;
} id3v1_1; // 128 bytes in size
id3v1_1 *gpTAG;
#define BYTESREMAINING_ACCOUNT_FOR_REAR_TAG(_pvData, _iBytesRemaining) \
\
/* account for trailing ID3 tag in _iBytesRemaining */ \
gpTAG = (id3v1_1*) (((byte *)_pvData + _iBytesRemaining)-sizeof(id3v1_1)); /* sizeof = 128 */ \
if (!strncmp(gpTAG->id, "TAG", 3)) \
{ \
_iBytesRemaining -= sizeof(id3v1_1); \
}
/******** pcm buffer ********/
#define PCM_BUFBYTES 60000U // more than enough to cover the largest that one packet will ever expand to
char PCM_Buffer[PCM_BUFBYTES]; // better off being declared, so we don't do mallocs in this module (MAC reasons)
typedef struct
{
int (*decode_init) (MPEG_HEAD * h, int framebytes_arg,
int reduction_code, int transform_code,
int convert_code, int freq_limit);
void (*decode_info) (DEC_INFO * info);
IN_OUT(*decode) (unsigned char *bs, short *pcm, unsigned char *pNextByteAfterData);
}
AUDIO;
#if 0
// fuck this...
static AUDIO audio_table[2][2] =
{
{
{audio_decode_init, audio_decode_info, audio_decode},
{audio_decode8_init, audio_decode8_info, audio_decode8},
},
{
{i_audio_decode_init, i_audio_decode_info, i_audio_decode},
{audio_decode8_init, audio_decode8_info, audio_decode8},
}
};
#else
static AUDIO audio_table[2][2] =
{
{
{audio_decode_init, audio_decode_info, audio_decode},
{audio_decode_init, audio_decode_info, audio_decode},
},
{
{audio_decode_init, audio_decode_info, audio_decode},
{audio_decode_init, audio_decode_info, audio_decode},
}
};
#endif
static const AUDIO audio = {audio_decode_init, audio_decode_info, audio_decode}; //audio_table[0][0];
// Do NOT change these, ever!!!!!!!!!!!!!!!!!!
//
const int reduction_code = 0; // unpack at full sample rate output
const int convert_code_mono = 1;
const int convert_code_stereo = 0;
const int freq_limit = 24000; // no idea what this is about, but it's always this value so...
// the entire decode mechanism uses this now...
//
MP3STREAM _MP3Stream;
LP_MP3STREAM pMP3Stream = &_MP3Stream;
int bFastEstimateOnly = 0; // MUST DEFAULT TO THIS VALUE!!!!!!!!!
// char *return is NZ for any errors (no trailing CR!)
//
char *C_MP3_IsValid(void *pvData, int iDataLen, int bStereoDesired)
{
// char sTemp[1024]; /////////////////////////////////////////////////
unsigned int iRealDataStart;
MPEG_HEAD head;
DEC_INFO decinfo;
int iBitRate;
int iFrameBytes;
#ifdef _DEBUG
// int iIgnoreThisForNowIJustNeedItToBreakpointOnToReadAValue = sizeof(MP3STREAM);
// iBitRate = iIgnoreThisForNowIJustNeedItToBreakpointOnToReadAValue; // get rid of unused-variable warning
#endif
memset(pMP3Stream,0,sizeof(*pMP3Stream));
iFrameBytes = head_info3( pvData, iDataLen/2, &head, &iBitRate, &iRealDataStart);
if (iFrameBytes == 0)
{
return "MP3ERR: Bad or unsupported file!";
}
// check for files with bad frame unpack sizes (that would crash the game), or stereo files.
//
// although the decoder can convert stereo to mono (apparently), we want to know about stereo files
// because they're a waste of source space... (all FX are mono, and moved via panning)
//
if (head.mode != 3 && !bStereoDesired && iDataLen > 98000) //3 seems to mean mono
{// we'll allow it for small files even if stereo
if ( iDataLen != 1050024 ) //fixme, make cinematic_1 play as music instead
{
return "MP3ERR: Sound file is stereo!";
}
}
if (audio.decode_init(&head, iFrameBytes, reduction_code, iRealDataStart, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit))
{
if (bStereoDesired)
{
if (pMP3Stream->outbytes > 4608)
{
return "MP3ERR: Source file has output packet size > 2304 (*2 for stereo) bytes!";
}
}
else
{
if (pMP3Stream->outbytes > 2304)
{
return "MP3ERR: Source file has output packet size > 2304 bytes!";
}
}
audio.decode_info(&decinfo);
if (decinfo.bits != 16)
{
return "MP3ERR: Source file is not 16bit!"; // will this ever happen? oh well...
}
if (decinfo.samprate != 44100)
{
return "MP3ERR: Source file is not sampled @ 44100!";
}
if (bStereoDesired && decinfo.channels != 2)
{
return "MP3ERR: Source file is not stereo!"; // sod it, I'm going to count this as an error now
}
}
else
{
return "MP3ERR: Decoder failed to initialise";
}
// file seems to be valid...
//
return NULL;
}
// char *return is NZ for any errors (no trailing CR!)
//
char* C_MP3_GetHeaderData(void *pvData, int iDataLen, int *piRate, int *piWidth, int *piChannels, int bStereoDesired)
{
unsigned int iRealDataStart;
MPEG_HEAD head;
DEC_INFO decinfo;
int iBitRate;
int iFrameBytes;
memset(pMP3Stream,0,sizeof(*pMP3Stream));
iFrameBytes = head_info3( pvData, iDataLen/2, &head, &iBitRate, &iRealDataStart);
if (iFrameBytes == 0)
{
return "MP3ERR: Bad or unsupported file!";
}
if (audio.decode_init(&head, iFrameBytes, reduction_code, iRealDataStart, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit))
{
audio.decode_info(&decinfo);
*piRate = decinfo.samprate; // rate (eg 22050, 44100 etc)
*piWidth = decinfo.bits/8; // 1 for 8bit, 2 for 16 bit
*piChannels = decinfo.channels; // 1 for mono, 2 for stereo
}
else
{
return "MP3ERR: Decoder failed to initialise";
}
// everything ok...
//
return NULL;
}
// this duplicates work done in C_MP3_IsValid(), but it avoids global structs, and means that you can call this anytime
// if you just want info for some reason
//
// ( size is now workd out just by decompressing each packet header, not the whole stream. MUCH faster :-)
//
// char *return is NZ for any errors (no trailing CR!)
//
char *C_MP3_GetUnpackedSize(void *pvData, int iSourceBytesRemaining, int *piUnpackedSize, int bStereoDesired )
{
int iReadLimit;
unsigned int iRealDataStart;
MPEG_HEAD head;
int iBitRate;
char *pPCM_Buffer = PCM_Buffer;
char *psReturn = NULL;
// int iSourceReadIndex = 0;
int iDestWriteIndex = 0;
int iFrameBytes;
int iFrameCounter;
DEC_INFO decinfo;
IN_OUT x;
memset(pMP3Stream,0,sizeof(*pMP3Stream));
#define iSourceReadIndex iRealDataStart
// iFrameBytes = head_info2( pvData, 0, &head, &iBitRate);
iFrameBytes = head_info3( pvData, iSourceBytesRemaining/2, &head, &iBitRate, &iRealDataStart);
BYTESREMAINING_ACCOUNT_FOR_REAR_TAG(pvData, iSourceBytesRemaining)
iSourceBytesRemaining -= iRealDataStart;
iReadLimit = iSourceReadIndex + iSourceBytesRemaining;
if (iFrameBytes)
{
//pPCM_Buffer = Z_Malloc(PCM_BUFBYTES);
//if (pPCM_Buffer)
{
// init decoder...
if (audio.decode_init(&head, iFrameBytes, reduction_code, iRealDataStart, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit))
{
audio.decode_info(&decinfo);
// decode...
//
for (iFrameCounter = 0;;iFrameCounter++)
{
if ( iSourceBytesRemaining == 0 || iSourceBytesRemaining < iFrameBytes)
break; // end of file
bFastEstimateOnly = 1; ///////////////////////////////
x = audio.decode((unsigned char *)pvData + iSourceReadIndex, (short *) ((char *)pPCM_Buffer
//+ iDestWriteIndex // keep decoding over the same spot since we're only counting bytes in this function
),
(unsigned char *)pvData + iReadLimit
);
bFastEstimateOnly = 0; ///////////////////////////////
iSourceReadIndex += x.in_bytes;
iSourceBytesRemaining -= x.in_bytes;
iDestWriteIndex += x.out_bytes;
if (x.in_bytes <= 0)
{
//psReturn = "MP3ERR: Bad sync in file";
break;
}
}
*piUnpackedSize = iDestWriteIndex; // yeeehaaa!
}
else
{
psReturn = "MP3ERR: Decoder failed to initialise";
}
}
// else
// {
// psReturn = "MP3ERR: Unable to alloc temp decomp buffer";
// }
}
else
{
psReturn = "MP3ERR: Bad or Unsupported MP3 file!";
}
// if (pPCM_Buffer)
// {
// Z_Free(pPCM_Buffer);
// pPCM_Buffer = NULL; // I know, I know...
// }
return psReturn;
#undef iSourceReadIndex
}
char *C_MP3_UnpackRawPCM( void *pvData, int iSourceBytesRemaining, int *piUnpackedSize, void *pbUnpackBuffer, int bStereoDesired)
{
int iReadLimit;
unsigned int iRealDataStart;
MPEG_HEAD head;
int iBitRate;
char *psReturn = NULL;
// int iSourceReadIndex = 0;
int iDestWriteIndex = 0;
int iFrameBytes;
int iFrameCounter;
DEC_INFO decinfo;
IN_OUT x;
memset(pMP3Stream,0,sizeof(*pMP3Stream));
#define iSourceReadIndex iRealDataStart
// iFrameBytes = head_info2( pvData, 0, &head, &iBitRate);
iFrameBytes = head_info3( pvData, iSourceBytesRemaining/2, &head, &iBitRate, &iRealDataStart);
BYTESREMAINING_ACCOUNT_FOR_REAR_TAG(pvData, iSourceBytesRemaining)
iSourceBytesRemaining -= iRealDataStart;
iReadLimit = iSourceReadIndex + iSourceBytesRemaining;
if (iFrameBytes)
{
// if (1)////////////////////////pPCM_Buffer)
{
// init decoder...
if (audio.decode_init(&head, iFrameBytes, reduction_code, iRealDataStart, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit))
{
audio.decode_info(&decinfo);
// printf("\n output samprate = %6ld",decinfo.samprate);
// printf("\n output channels = %6d", decinfo.channels);
// printf("\n output bits = %6d", decinfo.bits);
// printf("\n output type = %6d", decinfo.type);
//===============
// decode...
//
for (iFrameCounter = 0;;iFrameCounter++)
{
if ( iSourceBytesRemaining == 0 || iSourceBytesRemaining < iFrameBytes)
break; // end of file
x = audio.decode((unsigned char *)pvData + iSourceReadIndex, (short *) ((char *)pbUnpackBuffer + iDestWriteIndex),
(unsigned char *)pvData + iReadLimit
);
iSourceReadIndex += x.in_bytes;
iSourceBytesRemaining -= x.in_bytes;
iDestWriteIndex += x.out_bytes;
if (x.in_bytes <= 0)
{
//psReturn = "MP3ERR: Bad sync in file";
break;
}
}
*piUnpackedSize = iDestWriteIndex; // yeeehaaa!
}
else
{
psReturn = "MP3ERR: Decoder failed to initialise";
}
}
}
else
{
psReturn = "MP3ERR: Bad or Unsupported MP3 file!";
}
return psReturn;
#undef iSourceReadIndex
}
// called once, after we've decided to keep something as MP3. This just sets up the decoder for subsequent stream-calls.
//
// (the struct pSFX_MP3Stream is cleared internally, so pass as args anything you want stored in it)
//
// char * return is NULL for ok, else error string
//
char *C_MP3Stream_DecodeInit( LP_MP3STREAM pSFX_MP3Stream, void *pvSourceData, int iSourceBytesRemaining,
int iGameAudioSampleRate, int iGameAudioSampleBits, int bStereoDesired )
{
char *psReturn = NULL;
MPEG_HEAD head; // only relevant within this function during init
DEC_INFO decinfo; // " "
int iBitRate; // not used after being filled in by head_info3()
pMP3Stream = pSFX_MP3Stream;
memset(pMP3Stream,0,sizeof(*pMP3Stream));
pMP3Stream->pbSourceData = (byte *) pvSourceData;
pMP3Stream->iSourceBytesRemaining = iSourceBytesRemaining;
pMP3Stream->iSourceFrameBytes = head_info3( (byte *) pvSourceData, iSourceBytesRemaining/2, &head, &iBitRate, (unsigned int*)&pMP3Stream->iSourceReadIndex );
// hack, do NOT do this for stereo, since music files are now streamed and therefore the data isn't actually fully
// loaded at this point, only about 4k or so for the header is actually in memory!!!...
//
if (!bStereoDesired)
{
BYTESREMAINING_ACCOUNT_FOR_REAR_TAG(pvSourceData, pMP3Stream->iSourceBytesRemaining);
pMP3Stream->iSourceBytesRemaining -= pMP3Stream->iSourceReadIndex;
}
// backup a couple of fields so we can play this again later...
//
pMP3Stream->iRewind_SourceReadIndex = pMP3Stream->iSourceReadIndex;
pMP3Stream->iRewind_SourceBytesRemaining= pMP3Stream->iSourceBytesRemaining;
assert(pMP3Stream->iSourceFrameBytes);
if (pMP3Stream->iSourceFrameBytes)
{
if (audio.decode_init(&head, pMP3Stream->iSourceFrameBytes, reduction_code, pMP3Stream->iSourceReadIndex, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit))
{
pMP3Stream->iRewind_FinalReductionCode = reduction_code; // default = 0 (no reduction), 1=half, 2 = quarter
pMP3Stream->iRewind_FinalConvertCode = bStereoDesired?convert_code_stereo:convert_code_mono;
// default = 1 (mono), OR with 8 for 8-bit output
// only now can we ask what kind of properties this file has, and then adjust to fit what the game wants...
//
audio.decode_info(&decinfo);
// printf("\n output samprate = %6ld",decinfo.samprate);
// printf("\n output channels = %6d", decinfo.channels);
// printf("\n output bits = %6d", decinfo.bits);
// printf("\n output type = %6d", decinfo.type);
// decoder offers half or quarter rate adjustement only...
//
if (iGameAudioSampleRate == decinfo.samprate>>1)
pMP3Stream->iRewind_FinalReductionCode = 1;
else
if (iGameAudioSampleRate == decinfo.samprate>>2)
pMP3Stream->iRewind_FinalReductionCode = 2;
if (iGameAudioSampleBits == decinfo.bits>>1) // if game wants 8 bit sounds, then setup for that
pMP3Stream->iRewind_FinalConvertCode |= 8;
if (audio.decode_init(&head, pMP3Stream->iSourceFrameBytes, pMP3Stream->iRewind_FinalReductionCode, pMP3Stream->iSourceReadIndex, pMP3Stream->iRewind_FinalConvertCode, freq_limit))
{
audio.decode_info(&decinfo);
#ifdef _DEBUG
assert( iGameAudioSampleRate == decinfo.samprate );
assert( iGameAudioSampleBits == decinfo.bits );
#endif
// sod it, no harm in one last check... (should never happen)
//
if ( iGameAudioSampleRate != decinfo.samprate || iGameAudioSampleBits != decinfo.bits )
{
psReturn = "MP3ERR: Decoder unable to convert to current game audio settings";
}
}
else
{
psReturn = "MP3ERR: Decoder failed to initialise for pass 2 sample adjust";
}
}
else
{
psReturn = "MP3ERR: Decoder failed to initialise";
}
}
else
{
psReturn = "MP3ERR: Errr.... something's broken with this MP3 file"; // should never happen by this point
}
// restore global stream ptr before returning to normal functions (so the rest of the MP3 code still works)...
//
pMP3Stream = &_MP3Stream;
return psReturn;
}
// return value is decoded bytes for this packet, which is effectively a BOOL, NZ for not finished decoding yet...
//
unsigned int C_MP3Stream_Decode( LP_MP3STREAM pSFX_MP3Stream, int bFastForwarding )
{
unsigned int uiDecoded = 0; // default to "finished"
IN_OUT x;
pMP3Stream = pSFX_MP3Stream;
do
{
if ( pSFX_MP3Stream->iSourceBytesRemaining == 0 )//|| pSFX_MP3Stream->iSourceBytesRemaining < pSFX_MP3Stream->iSourceFrameBytes)
{
uiDecoded = 0; // finished
break;
}
bFastEstimateOnly = bFastForwarding; ///////////////////////////////
x = audio.decode(pSFX_MP3Stream->pbSourceData + pSFX_MP3Stream->iSourceReadIndex, (short *) (pSFX_MP3Stream->bDecodeBuffer),
pSFX_MP3Stream->pbSourceData + pSFX_MP3Stream->iRewind_SourceReadIndex + pSFX_MP3Stream->iRewind_SourceBytesRemaining
);
bFastEstimateOnly = 0; ///////////////////////////////
#ifdef _DEBUG
pSFX_MP3Stream->iSourceFrameCounter++;
#endif
pSFX_MP3Stream->iSourceReadIndex += x.in_bytes;
pSFX_MP3Stream->iSourceBytesRemaining -= x.in_bytes;
pSFX_MP3Stream->iBytesDecodedTotal += x.out_bytes;
pSFX_MP3Stream->iBytesDecodedThisPacket = x.out_bytes;
uiDecoded = x.out_bytes;
if (x.in_bytes <= 0)
{
//psReturn = "MP3ERR: Bad sync in file";
uiDecoded= 0; // finished
break;
}
}
#pragma warning (disable : 4127 ) // conditional expression is constant
while (0); // <g>
#pragma warning (default : 4127 ) // conditional expression is constant
// restore global stream ptr before returning to normal functions (so the rest of the MP3 code still works)...
//
pMP3Stream = &_MP3Stream;
return uiDecoded;
}
// ret is char* errstring, else NULL for ok
//
char *C_MP3Stream_Rewind( LP_MP3STREAM pSFX_MP3Stream )
{
char* psReturn = NULL;
MPEG_HEAD head; // only relevant within this function during init
int iBitRate; // ditto
int iNULL;
pMP3Stream = pSFX_MP3Stream;
pMP3Stream->iSourceReadIndex = pMP3Stream->iRewind_SourceReadIndex;
pMP3Stream->iSourceBytesRemaining = pMP3Stream->iRewind_SourceBytesRemaining; // already adjusted for tags etc
// I'm not sure that this is needed, but where else does decode_init get passed useful data ptrs?...
//
if (pMP3Stream->iSourceFrameBytes == head_info3( pMP3Stream->pbSourceData, pMP3Stream->iSourceBytesRemaining/2, &head, &iBitRate, (unsigned int*)&iNULL ) )
{
if (audio.decode_init(&head, pMP3Stream->iSourceFrameBytes, pMP3Stream->iRewind_FinalReductionCode, pMP3Stream->iSourceReadIndex, pMP3Stream->iRewind_FinalConvertCode, freq_limit))
{
// we should always get here...
//
}
else
{
psReturn = "MP3ERR: Failed to re-init decoder for rewind!";
}
}
else
{
psReturn = "MP3ERR: Frame bytes mismatch during rewind header-read!";
}
// restore global stream ptr before returning to normal functions (so the rest of the MP3 code still works)...
//
pMP3Stream = &_MP3Stream;
return psReturn;
}