Files
xash3d-fwgs/engine/common/soundlib/snd_mp3.c

940 lines
22 KiB
C

/*
snd_mp3.c - mp3 format loading and streaming
Copyright (C) 2010 Uncle Mike
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 3 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.
*/
#include "soundlib.h"
#if !XASH_PSP
#include "libmpg/libmpg.h"
#pragma pack( push, 1 )
typedef struct did3v2_header_s
{
char ident[3]; // must be "ID3"
uint8_t major_ver; // must be 4
uint8_t minor_ver; // must be 0
uint8_t flags;
uint32_t length; // size of extended header, padding and frames
} did3v2_header_t;
STATIC_ASSERT( sizeof( did3v2_header_t ) == 10,
"invalid did3v2_header_t size" );
typedef struct did3v2_extended_header_s
{
uint32_t length;
uint8_t flags_length;
uint8_t flags[1];
} did3v2_extended_header_t;
STATIC_ASSERT( sizeof( did3v2_extended_header_t ) == 6,
"invalid did3v2_extended_header_t size" );
typedef struct did3v2_frame_s
{
char frame_id[4];
uint32_t length;
uint8_t flags[2];
} did3v2_frame_t;
STATIC_ASSERT( sizeof( did3v2_frame_t ) == 10,
"invalid did3v2_frame_t size" );
#pragma pack( pop )
typedef enum did3v2_header_flags_e
{
ID3V2_HEADER_UNSYHCHRONIZATION = BIT( 7U ),
ID3V2_HEADER_EXTENDED_HEADER = BIT( 6U ),
ID3V2_HEADER_EXPERIMENTAL = BIT( 5U ),
ID3V2_HEADER_FOOTER_PRESENT = BIT( 4U ),
} did3v2_header_flags_t;
#define CHECK_IDENT( ident, b0, b1, b2 ) ((( ident )[0]) == ( b0 ) && (( ident )[1]) == ( b1 ) && (( ident )[2]) == ( b2 ))
#define CHECK_FRAME_ID( ident, b0, b1, b2, b3 ) ( CHECK_IDENT( ident, b0, b1, b2 ) && (( ident )[3]) == ( b3 ))
static uint32_t Sound_ParseSynchInteger( uint32_t v )
{
uint32_t res = 0;
// read as big endian
res |= (( v >> 24 ) & 0x7f ) << 0;
res |= (( v >> 16 ) & 0x7f ) << 7;
res |= (( v >> 8 ) & 0x7f ) << 14;
res |= (( v >> 0 ) & 0x7f ) << 21;
return res;
}
static void Sound_HandleCustomID3Comment( const char *key, const char *value )
{
if( !Q_strcmp( key, "LOOP_START" ) || !Q_strcmp( key, "LOOPSTART" ))
sound.loopstart = Q_atoi( value );
// unknown comment is not an error
}
static qboolean Sound_ParseID3Frame( const did3v2_frame_t *frame, const byte *buffer, size_t frame_length )
{
if( CHECK_FRAME_ID( frame->frame_id, 'T', 'X', 'X', 'X' ))
{
string key, value;
int32_t key_len, value_len;
if( buffer[0] == 0x00 || buffer[0] == 0x03 )
{
key_len = Q_strncpy( key, &buffer[1], sizeof( key ));
value_len = frame_length - (1 + key_len + 1);
if( value_len <= 0 || value_len >= sizeof( value ))
{
Con_Printf( S_ERROR "Sound_ParseID3Frame: invalid TXXX description, possibly broken file.\n" );
return false;
}
memcpy( value, &buffer[1 + key_len + 1], value_len );
value[value_len + 1] = 0;
Sound_HandleCustomID3Comment( key, value );
}
else
{
if( buffer[0] == 0x01 || buffer[0] == 0x02 ) // UTF-16 with BOM
Con_Printf( S_ERROR "Sound_ParseID3Frame: UTF-16 encoding is unsupported. Use UTF-8 or ISO-8859!\n" );
else
Con_Printf( S_ERROR "Sound_ParseID3Frame: unknown TXXX tag encoding %d, possibly broken file.\n", buffer[0] );
return false;
}
}
return true;
}
static qboolean Sound_ParseID3Tag( const byte *buffer, fs_offset_t filesize )
{
const did3v2_header_t *header = (const did3v2_header_t *)buffer;
const byte *buffer_begin = buffer;
uint32_t tag_length;
if( filesize < sizeof( *header ))
return false;
buffer += sizeof( *header );
// support only id3v2
if( !CHECK_IDENT( header->ident, 'I', 'D', '3' ))
{
// old id3v1 header found
if( CHECK_IDENT( header->ident, 'T', 'A', 'G' ))
Con_Printf( S_ERROR "Sound_ParseID3Tag: ID3v1 is not supported! Convert to ID3v2.4!\n" );
return true; // missing tag header is not an error
}
// support only latest id3 v2.4
if( header->major_ver != 4 || header->minor_ver == 0xff )
{
Con_Printf( S_ERROR "Sound_ParseID3Tag: invalid ID3v2 tag version 2.%d.%d. Convert to ID3v2.4!\n", header->major_ver, header->minor_ver );
return false;
}
tag_length = Sound_ParseSynchInteger( header->length );
if( tag_length > filesize - sizeof( *header ))
{
Con_Printf( S_ERROR "Sound_ParseID3Tag: invalid tag length %u, possibly broken file.\n", tag_length );
return false;
}
// just skip extended header
if( FBitSet( header->flags, ID3V2_HEADER_EXTENDED_HEADER ))
{
const did3v2_extended_header_t *ext_header = (const did3v2_extended_header_t *)buffer;
uint32_t ext_length = Sound_ParseSynchInteger( ext_header->length );
if( ext_length > tag_length )
{
Con_Printf( S_ERROR "Sound_ParseID3Tag: invalid extended header length %u, possibly broken file.\n", ext_length );
return false;
}
buffer += ext_length;
}
while( buffer - buffer_begin < tag_length )
{
const did3v2_frame_t *frame = (const did3v2_frame_t *)buffer;
uint32_t frame_length = Sound_ParseSynchInteger( frame->length );
if( frame_length > tag_length )
{
Con_Printf( S_ERROR "Sound_ParseID3Tag: invalid frame length %u, possibly broken file.\n", frame_length );
return false;
}
buffer += sizeof( *frame );
// parse can fail, but it's ok to continue
Sound_ParseID3Frame( frame, buffer, frame_length );
buffer += frame_length;
}
return true;
}
#if XASH_ENGINE_TESTS
int EXPORT Fuzz_Sound_ParseID3Tag( const uint8_t *Data, size_t Size )
{
memset( &sound, 0, sizeof( sound ));
Sound_ParseID3Tag( Data, Size );
return 0;
}
#endif
/*
=================================================================
MPEG decompression
=================================================================
*/
qboolean Sound_LoadMPG( const char *name, const byte *buffer, fs_offset_t filesize )
{
void *mpeg;
size_t pos = 0;
size_t bytesWrite = 0;
byte out[OUTBUF_SIZE];
size_t outsize, padsize;
int ret;
wavinfo_t sc;
// load the file
if( !buffer || filesize < FRAME_SIZE )
return false;
// couldn't create decoder
if(( mpeg = create_decoder( &ret )) == NULL )
return false;
if( ret ) Con_DPrintf( S_ERROR "%s\n", get_error( mpeg ));
// trying to read header
if( !feed_mpeg_header( mpeg, buffer, FRAME_SIZE, filesize, &sc ))
{
Con_DPrintf( S_ERROR "Sound_LoadMPG: failed to load (%s): %s\n", name, get_error( mpeg ));
close_decoder( mpeg );
return false;
}
sound.channels = sc.channels;
sound.rate = sc.rate;
sound.width = 2; // always 16-bit PCM
sound.loopstart = -1;
sound.size = ( sound.channels * sound.rate * sound.width ) * ( sc.playtime / 1000 ); // in bytes
padsize = sound.size % FRAME_SIZE;
pos += FRAME_SIZE; // evaluate pos
if( !Sound_ParseID3Tag( buffer, filesize ))
{
Con_DPrintf( S_WARN "Sound_LoadMPG: (%s) failed to extract LOOP_START tag\n", name );
sound.loopstart = -1;
}
if( !sound.size )
{
// bad mpeg file ?
Con_DPrintf( S_ERROR "Sound_LoadMPG: (%s) is probably corrupted\n", name );
close_decoder( mpeg );
return false;
}
// add sentinel make sure we not overrun
sound.wav = (byte *)Mem_Calloc( host.soundpool, sound.size + padsize );
sound.type = WF_PCMDATA;
// decompress mpg into pcm wav format
while( bytesWrite < sound.size )
{
int size;
if( feed_mpeg_stream( mpeg, NULL, 0, out, &outsize ) != MP3_OK && outsize <= 0 )
{
const byte *data = buffer + pos;
int bufsize;
// if there are no bytes remainig so we can decompress the new frame
if( pos + FRAME_SIZE > filesize )
bufsize = ( filesize - pos );
else bufsize = FRAME_SIZE;
pos += bufsize;
if( feed_mpeg_stream( mpeg, data, bufsize, out, &outsize ) != MP3_OK )
break; // there was end of the stream
}
if( bytesWrite + outsize > sound.size )
size = ( sound.size - bytesWrite );
else size = outsize;
memcpy( &sound.wav[bytesWrite], out, size );
bytesWrite += size;
}
sound.samples = bytesWrite / ( sound.width * sound.channels );
close_decoder( mpeg );
return true;
}
/*
=================
FS_SeekEx
=================
*/
fs_offset_t FS_SeekEx( file_t *file, fs_offset_t offset, int whence )
{
return FS_Seek( file, offset, whence ) == -1 ? -1 : FS_Tell( file );
}
/*
=================
Stream_OpenMPG
=================
*/
stream_t *Stream_OpenMPG( const char *filename )
{
stream_t *stream;
void *mpeg;
file_t *file;
int ret;
wavinfo_t sc;
file = FS_Open( filename, "rb", false );
if( !file ) return NULL;
// at this point we have valid stream
stream = Mem_Calloc( host.soundpool, sizeof( stream_t ));
stream->file = file;
stream->pos = 0;
// couldn't create decoder
if(( mpeg = create_decoder( &ret )) == NULL )
{
Con_DPrintf( S_ERROR "Stream_OpenMPG: couldn't create decoder: %s\n", get_error( mpeg ) );
Mem_Free( stream );
FS_Close( file );
return NULL;
}
if( ret ) Con_DPrintf( S_ERROR "%s\n", get_error( mpeg ));
// trying to open stream and read header
if( !open_mpeg_stream( mpeg, file, (void*)FS_Read, (void*)FS_SeekEx, &sc ))
{
Con_DPrintf( S_ERROR "Stream_OpenMPG: failed to load (%s): %s\n", filename, get_error( mpeg ));
close_decoder( mpeg );
Mem_Free( stream );
FS_Close( file );
return NULL;
}
stream->buffsize = 0; // how many samples left from previous frame
stream->channels = sc.channels;
stream->rate = sc.rate;
stream->width = 2; // always 16 bit
stream->ptr = mpeg;
stream->type = WF_MPGDATA;
return stream;
}
/*
=================
Stream_ReadMPG
assume stream is valid
=================
*/
int Stream_ReadMPG( stream_t *stream, int needBytes, void *buffer )
{
// buffer handling
int bytesWritten = 0;
void *mpg;
mpg = stream->ptr;
while( 1 )
{
byte *data;
int outsize;
if( !stream->buffsize )
{
if( read_mpeg_stream( mpg, (byte*)stream->temp, &stream->pos ) != MP3_OK )
break; // there was end of the stream
}
// check remaining size
if( bytesWritten + stream->pos > needBytes )
outsize = ( needBytes - bytesWritten );
else outsize = stream->pos;
// copy raw sample to output buffer
data = (byte *)buffer + bytesWritten;
memcpy( data, &stream->temp[stream->buffsize], outsize );
bytesWritten += outsize;
stream->pos -= outsize;
stream->buffsize += outsize;
// continue from this sample on a next call
if( bytesWritten >= needBytes )
return bytesWritten;
stream->buffsize = 0; // no bytes remaining
}
return 0;
}
/*
=================
Stream_SetPosMPG
assume stream is valid
=================
*/
int Stream_SetPosMPG( stream_t *stream, int newpos )
{
if( set_stream_pos( stream->ptr, newpos ) != -1 )
{
// flush any previous data
stream->buffsize = 0;
return true;
}
// failed to seek for some reasons
return false;
}
/*
=================
Stream_GetPosMPG
assume stream is valid
=================
*/
int Stream_GetPosMPG( stream_t *stream )
{
return get_stream_pos( stream->ptr );
}
/*
=================
Stream_FreeMPG
assume stream is valid
=================
*/
void Stream_FreeMPG( stream_t *stream )
{
if( stream->ptr )
{
close_decoder( stream->ptr );
stream->ptr = NULL;
}
if( stream->file )
{
FS_Close( stream->file );
stream->file = NULL;
}
Mem_Free( stream );
}
#else // XASH_PSP
#include "platform/psp/scemp3/pspmp3.h"
/*
=================================================================
PSP MPEG decoding
=================================================================
*/
#define MP3_ID3V1_ID "TAG"
#define MP3_ID3V1_ID_SZ 3
#define MP3_ID3V1_SZ 128
#define MP3_ID3V2_ID "ID3"
#define MP3_ID3V2_ID_SZ 3
#define MP3_ID3V2_SIZE_OFF 6
#define MP3_ID3V2_SIZE_SZ 4
#define MP3_ID3V2_HEADER_SZ 10
#define MP3_ERRORS_MAX 3
#define PCM_WIDTH 2 // always 16-bit PCM
#define PCM_CHANNELS 2 // always 2
#define MP3_BUFFER_SIZE ( 128 * 1024 )
#define PCM_BUFFER_SIZE (( 1152 * PCM_WIDTH * PCM_CHANNELS ) * 2 ) // framesample * width * channels * 2(double buffer)
typedef struct
{
int handle;
int cachePos;
int frameCount;
byte *pcmTempPtr;
} mp3_decoder_t;
/*
=================
Sound_GetID3V2SizeMPG
=================
*/
__inline int Sound_GetID3V2SizeMPG( const byte *tagSize )
{
int size;
byte *sizePtr = (byte*)&size;
// 7 bit per byte, invert endian
sizePtr[0] = (( tagSize[3] >> 0 ) & 0x7F ) | (( tagSize[2] & 0x01 ) << 7 );
sizePtr[1] = (( tagSize[2] >> 1 ) & 0x3F ) | (( tagSize[1] & 0x03 ) << 6 );
sizePtr[2] = (( tagSize[1] >> 2 ) & 0x1F ) | (( tagSize[0] & 0x07 ) << 5 );
sizePtr[3] = (( tagSize[0] >> 3 ) & 0x0F );
return size;
}
#if 0
/*
=================
Sound_FindHeadMPG
=================
*/
fs_offset_t Sound_FindHeadMPG( const byte *buffer, fs_offset_t filesize )
{
fs_offset_t result;
result = 0;
if( filesize >= MP3_ID3V2_HEADER_SZ )
{
if( !memcmp( buffer, MP3_ID3V2_ID, MP3_ID3V2_ID_SZ ))
result = Sound_GetID3V2SizeMPG( &buffer[MP3_ID3V2_SIZE_OFF] ) + MP3_ID3V2_HEADER_SZ;
}
return result;
}
/*
=================
Sound_FindTailMPG
=================
*/
fs_offset_t Sound_FindTailMPG( const byte *buffer, fs_offset_t filesize )
{
fs_offset_t result;
result = filesize;
if( filesize >= MP3_ID3V1_SZ )
{
if( !memcmp( &buffer[filesize - MP3_ID3V1_SZ], MP3_ID3V1_ID, MP3_ID3V1_ID_SZ ))
result -= MP3_ID3V1_SZ;
}
return result;
}
#endif
qboolean Sound_LoadMPG( const char *name, const byte *buffer, fs_offset_t filesize )
{
#if 0
int status;
int handle;
size_t bytesWrite = 0;
byte out[PCM_BUFFER_SIZE] __attribute__((aligned(64)));
fs_offset_t headOffset;
fs_offset_t tailOffset;
fs_offset_t contentSize;
int frameSample;
uint bps;
int frameLen;
int frameSize;
int frameCount;
headOffset = Sound_FindHeadMPG( buffer, filesize );
tailOffset = Sound_FindTailMPG( buffer, filesize );
contentSize = tailOffset - headOffset;
// TODO: read Xing header
handle = sceMp3ReserveMp3Handle( NULL );
if ( handle < 0 )
{
Con_DPrintf( S_ERROR "sceMp3ReserveMp3Handle returned 0x%08X\n", handle );
return false;
}
status = sceMp3LowLevelInit( handle, &buffer[headOffset] );
if ( status < 0 )
{
Con_DPrintf( S_ERROR "sceMp3LowLevelInit returned 0x%08X\n", status );
return false;
}
frameSample = sceMp3GetMaxOutputSample( handle );
frameSize = (( frameSample / 8 ) * sceMp3GetBitRate( handle ) * 1000 ) / sceMp3GetSamplingRate( handle );
frameCount = contentSize / frameSize;
sound.channels = sceMp3GetMp3ChannelNum( handle );
sound.rate = sceMp3GetSamplingRate( handle );
sound.width = PCM_WIDTH; // always 16-bit PCM
sound.loopstart = -1;
sound.size = (frameCount * frameSample) * sound.channels * sound.width; // invalid for VBR
/*
status = sceMp3LowLevelDecode(handle, &buffer[headOffset + mp3usedsize], &mp3usedsize, pcm, &pcmoutsize);
if(status < 0)
{
Con_DPrintf( S_ERROR "sceMp3LowLevelDecode returned 0x%08X\n", status);
}
sceMp3ReleaseMp3Handle( handle );
*/
return true;
#else
Con_DPrintf( S_ERROR "Sound_LoadMPG unimplemented function!\n");
return false;
#endif
}
/*
=================
Stream_FindHeadMPG
=================
*/
SceOff Stream_FindHeadMPG( file_t *file )
{
SceOff result;
byte tagId[MP3_ID3V2_ID_SZ];
byte tagSize[MP3_ID3V2_SIZE_SZ];
result = 0;
FS_Seek( file, 0, SEEK_SET );
if( FS_Read( file, tagId, MP3_ID3V2_ID_SZ) < MP3_ID3V2_ID_SZ )
{
Con_DPrintf( S_ERROR "Sound_Mp3FindHead: ID3V2 ID read error\n");
return result;
}
if( !memcmp( tagId, MP3_ID3V2_ID, MP3_ID3V2_ID_SZ ))
{
FS_Seek( file, MP3_ID3V2_SIZE_OFF, SEEK_SET );
if( FS_Read( file, tagSize, MP3_ID3V2_SIZE_SZ ) < MP3_ID3V2_SIZE_SZ )
Con_DPrintf( S_ERROR "Sound_Mp3FindHead: ID3V2 SIZE read error\n");
else result = Sound_GetID3V2SizeMPG( tagSize ) + MP3_ID3V2_HEADER_SZ;
}
return result;
}
/*
=================
Stream_FindTailMPG
=================
*/
SceOff Stream_FindTailMPG( file_t *file )
{
SceOff result;
byte tagId[MP3_ID3V1_ID_SZ];
result = FS_FileLength( file );
FS_Seek( file, result - MP3_ID3V1_SZ, SEEK_SET );
if( FS_Read( file, tagId, MP3_ID3V1_ID_SZ ) < MP3_ID3V1_ID_SZ )
{
Con_DPrintf( S_ERROR "Sound_Mp3FindTail: ID3V1 ID read error\n");
return result;
}
if( !memcmp( tagId, MP3_ID3V1_ID, MP3_ID3V1_ID_SZ ))
result -= MP3_ID3V1_SZ;
return result;
}
/*
=================
Stream_FillBufferMPG
=================
*/
int Stream_FillBufferMPG( file_t *file, mp3_decoder_t *desc )
{
int status;
byte *dstPtr;
int dstSize;
int dstPos;
fs_offset_t readSize;
if( sceMp3CheckStreamDataNeeded( desc->handle ) <= 0 )
return 0;
// get Info on the stream (where to fill to, how much to fill, where to fill from)
status = sceMp3GetInfoToAddStreamData( desc->handle, &dstPtr, &dstSize, &dstPos );
if( status < 0 )
{
Con_DPrintf( S_ERROR "sceMp3GetInfoToAddStreamData returned 0x%08X\n", status);
return status;
}
// seek file to position requested
if( desc->cachePos != dstPos )
{
FS_Seek( file, dstPos, SEEK_SET );
desc->cachePos = dstPos;
}
// read the amount of data
readSize = FS_Read( file, dstPtr, dstSize );
desc->cachePos += ( int )readSize;
// notify mp3 library about how much we really wrote to the stream buffer
status = sceMp3NotifyAddStreamData( desc->handle, ( int )readSize );
if ( status < 0 )
Con_DPrintf( S_ERROR "sceMp3NotifyAddStreamData returned 0x%08X\n", status);
return status;
}
/*
=================
Stream_OpenMPG
=================
*/
stream_t *Stream_OpenMPG( const char *filename )
{
stream_t *stream;
mp3_decoder_t *desc;
file_t *file;
int status;
void *bufferBase;
file = FS_Open( filename, "rbh", false ); // hold mode
if( !file ) return NULL;
// at this point we have valid stream
stream = Mem_Calloc( host.soundpool, sizeof( stream_t ));
stream->file = file;
stream->pos = 0;
desc = Mem_Calloc( host.soundpool, sizeof( mp3_decoder_t ) + MP3_BUFFER_SIZE + PCM_BUFFER_SIZE + 63 );
bufferBase = (void *)(((( uintptr_t )desc + sizeof( mp3_decoder_t )) & (~( 64 - 1 ))) + 64 );
// reserve a mp3 handle
SceMp3InitArg mp3Init;
mp3Init.mp3StreamStart = Stream_FindHeadMPG( file );
mp3Init.mp3StreamEnd = Stream_FindTailMPG( file );
mp3Init.mp3Buf = bufferBase;
mp3Init.mp3BufSize = MP3_BUFFER_SIZE;
mp3Init.pcmBuf = (void *)(( uintptr_t )bufferBase + MP3_BUFFER_SIZE );
mp3Init.pcmBufSize = PCM_BUFFER_SIZE;
desc->cachePos = 0;
desc->pcmTempPtr = mp3Init.pcmBuf;
desc->handle = sceMp3ReserveMp3Handle( &mp3Init );
if ( desc->handle < 0 )
{
Con_DPrintf( S_ERROR "sceMp3ReserveMp3Handle returned 0x%08X\n", desc->handle );
Mem_Free( stream );
Mem_Free( desc );
FS_Close( file );
return NULL;
}
// Fill the stream buffer with some data so that sceMp3Init has something to work with
if( Stream_FillBufferMPG( file, desc ) != 0 )
{
Mem_Free( stream );
Mem_Free( desc );
FS_Close( file );
return NULL;
}
status = sceMp3Init( desc->handle );
if ( status < 0 )
{
Con_DPrintf( S_ERROR "sceMp3Init returned 0x%08X\n", status );
Mem_Free( stream );
Mem_Free( desc );
FS_Close( file );
return NULL;
}
desc->frameCount = sceMp3GetFrameNum( desc->handle );
stream->buffsize = 0; // how many samples left from previous frame
stream->channels = PCM_CHANNELS; // always 2 PCM channels
stream->rate = sceMp3GetSamplingRate( desc->handle );
stream->width = PCM_WIDTH; // always 16 bit PCM
stream->ptr = (void *)desc;
stream->type = WF_MPGDATA;
return stream;
}
/*
=================
Stream_ReadMPG
assume stream is valid
=================
*/
int Stream_ReadMPG( stream_t *stream, int needBytes, void *buffer )
{
// buffer handling
int bytesWritten = 0;
mp3_decoder_t *desc;
int errdec;
desc = ( mp3_decoder_t* )stream->ptr;
while(1)
{
byte *data;
int outsize;
if( !stream->buffsize )
{
// Check if we need to fill our stream buffer
if( Stream_FillBufferMPG( stream->file, desc ) != 0 )
return 0;
for( errdec = 0; errdec < MP3_ERRORS_MAX; errdec++ )
{
stream->pos = sceMp3Decode( desc->handle, (SceShort16**)&desc->pcmTempPtr );
if(( int )stream->pos >= 0 ) // decoding successful
{
break;
}
else if( stream->pos == 0x80671402 ) // next frame header
{
if( Stream_FillBufferMPG( stream->file, desc ) != 0 )
return 0;
}
else break;
}
if(( int )stream->pos < 0 )
{
if( stream->pos != 0x80671402 )
Con_DPrintf( S_ERROR "sceMp3Decode returned 0x%08X\n", stream->pos );
return 0; // ???
}
}
// check remaining size
if( bytesWritten + stream->pos > needBytes )
outsize = ( needBytes - bytesWritten );
else outsize = stream->pos;
// copy raw sample to output buffer
data = (byte *)buffer + bytesWritten;
memcpy( data, &desc->pcmTempPtr[stream->buffsize], outsize );
bytesWritten += outsize;
stream->pos -= outsize;
stream->buffsize += outsize;
// continue from this sample on a next call
if( bytesWritten >= needBytes )
return bytesWritten;
stream->buffsize = 0; // no bytes remaining
}
return 0;
}
/*
=================
Stream_SetPosMPG
assume stream is valid
=================
*/
int Stream_SetPosMPG( stream_t *stream, int newpos )
{
mp3_decoder_t *desc;
int frame;
int status;
desc = ( mp3_decoder_t* )stream->ptr;
// get frame num
frame = newpos / sceMp3GetMaxOutputSample( desc->handle ); // VBR?
if( frame < 1 || frame >= desc->frameCount - 1 )
return false;
status = sceMp3ResetPlayPositionByFrame( desc->handle, frame );
if( status < 0 )
{
Con_DPrintf( S_ERROR "sceMp3ResetPlayPositionByFrame returned 0x%08X\n", status );
// failed to seek for some reasons
return false;
}
// flush any previous data
stream->buffsize = 0;
return true;
}
/*
=================
Stream_GetPosMPG
assume stream is valid
=================
*/
int Stream_GetPosMPG( stream_t *stream )
{
mp3_decoder_t *desc;
desc = ( mp3_decoder_t * )stream->ptr;
return sceMp3GetSumDecodedSample( desc->handle );
}
/*
=================
Stream_FreeMPG
assume stream is valid
=================
*/
void Stream_FreeMPG( stream_t *stream )
{
mp3_decoder_t *desc;
desc = ( mp3_decoder_t * )stream->ptr;
if( desc )
{
sceMp3ReleaseMp3Handle( desc->handle );
Mem_Free( desc );
stream->ptr = NULL;
}
if( stream->file )
{
FS_Close( stream->file );
stream->file = NULL;
}
Mem_Free( stream );
}
#endif // XASH_PSP