517 lines
12 KiB
C
517 lines
12 KiB
C
//=======================================================================
|
|
// Copyright XashXT Group 2010 ©
|
|
// snd_ogg.c - ogg format load & save
|
|
//=======================================================================
|
|
|
|
#include "soundlib.h"
|
|
|
|
typedef __int64 ogg_int64_t;
|
|
typedef __int32 ogg_int32_t;
|
|
typedef unsigned __int32 ogg_uint32_t;
|
|
typedef __int16 ogg_int16_t;
|
|
typedef unsigned __int16 ogg_uint16_t;
|
|
|
|
/*
|
|
=======================================================================
|
|
OGG PACK DEFINITION
|
|
=======================================================================
|
|
*/
|
|
struct ogg_chain_s
|
|
{
|
|
void *ptr;
|
|
struct alloc_chain *next;
|
|
};
|
|
|
|
typedef struct oggpack_buffer_s
|
|
{
|
|
long endbyte;
|
|
int endbit;
|
|
byte *buffer;
|
|
byte *ptr;
|
|
long storage;
|
|
} oggpack_buffer_t;
|
|
|
|
typedef struct ogg_sync_state_s
|
|
{
|
|
byte *data;
|
|
int storage;
|
|
int fill;
|
|
int returned;
|
|
int unsynced;
|
|
int headerbytes;
|
|
int bodybytes;
|
|
} ogg_sync_state_t;
|
|
|
|
typedef struct ogg_stream_state_s
|
|
{
|
|
byte *body_data;
|
|
long body_storage;
|
|
long body_fill;
|
|
long body_returned;
|
|
int *lacing_vals;
|
|
ogg_int64_t *granule_vals;
|
|
long lacing_storage;
|
|
long lacing_fill;
|
|
long lacing_packet;
|
|
long lacing_returned;
|
|
byte header[282];
|
|
int header_fill;
|
|
int e_o_s;
|
|
int b_o_s;
|
|
long serialno;
|
|
long pageno;
|
|
ogg_int64_t packetno;
|
|
ogg_int64_t granulepos;
|
|
} ogg_stream_state_t;
|
|
|
|
/*
|
|
=======================================================================
|
|
VORBIS FILE DEFINITION
|
|
=======================================================================
|
|
*/
|
|
typedef struct vorbis_info_s
|
|
{
|
|
int version;
|
|
int channels;
|
|
long rate;
|
|
long bitrate_upper;
|
|
long bitrate_nominal;
|
|
long bitrate_lower;
|
|
long bitrate_window;
|
|
void *codec_setup;
|
|
} vorbis_info_t;
|
|
|
|
typedef struct vorbis_comment_s
|
|
{
|
|
char **user_comments;
|
|
int *comment_lengths;
|
|
int comments;
|
|
char *vendor;
|
|
} vorbis_comment_t;
|
|
|
|
typedef struct vorbis_dsp_state_s
|
|
{
|
|
int analysisp;
|
|
vorbis_info_t *vi;
|
|
float **pcm;
|
|
float **pcmret;
|
|
int pcm_storage;
|
|
int pcm_current;
|
|
int pcm_returned;
|
|
int preextrapolate;
|
|
int eofflag;
|
|
long lW;
|
|
long W;
|
|
long nW;
|
|
long centerW;
|
|
ogg_int64_t granulepos;
|
|
ogg_int64_t sequence;
|
|
ogg_int64_t glue_bits;
|
|
ogg_int64_t time_bits;
|
|
ogg_int64_t floor_bits;
|
|
ogg_int64_t res_bits;
|
|
void *backend_state;
|
|
} vorbis_dsp_state_t;
|
|
|
|
typedef struct vorbis_block_s
|
|
{
|
|
float **pcm;
|
|
oggpack_buffer_t opb;
|
|
long lW;
|
|
long W;
|
|
long nW;
|
|
int pcmend;
|
|
int mode;
|
|
int eofflag;
|
|
ogg_int64_t granulepos;
|
|
ogg_int64_t sequence;
|
|
vorbis_dsp_state_t *vd;
|
|
void *localstore;
|
|
long localtop;
|
|
long localalloc;
|
|
long totaluse;
|
|
struct ogg_chain_s *reap;
|
|
long glue_bits;
|
|
long time_bits;
|
|
long floor_bits;
|
|
long res_bits;
|
|
void *internal;
|
|
} vorbis_block_t;
|
|
|
|
typedef struct ov_callbacks_s
|
|
{
|
|
size_t (*read_func)( void *ptr, size_t size, size_t nmemb, void *datasource );
|
|
int (*seek_func)( void *datasource, ogg_int64_t offset, int whence );
|
|
int (*close_func)( void *datasource );
|
|
long (*tell_func)( void *datasource );
|
|
} ov_callbacks_t;
|
|
|
|
typedef struct
|
|
{
|
|
byte *buffer;
|
|
ogg_int64_t ind;
|
|
ogg_int64_t buffsize;
|
|
} ov_decode_t;
|
|
|
|
typedef struct vorbisfile_s
|
|
{
|
|
void *datasource;
|
|
int seekable;
|
|
ogg_int64_t offset;
|
|
ogg_int64_t end;
|
|
ogg_sync_state_t oy;
|
|
int links;
|
|
ogg_int64_t *offsets;
|
|
ogg_int64_t *dataoffsets;
|
|
long *serialnos;
|
|
ogg_int64_t *pcmlengths;
|
|
vorbis_info_t *vi;
|
|
vorbis_comment_t *vc;
|
|
ogg_int64_t pcm_offset;
|
|
int ready_state;
|
|
long current_serialno;
|
|
int current_link;
|
|
double bittrack;
|
|
double samptrack;
|
|
ogg_stream_state_t os;
|
|
vorbis_dsp_state_t vd;
|
|
vorbis_block_t vb;
|
|
ov_callbacks_t callbacks;
|
|
|
|
} vorbisfile_t;
|
|
|
|
// libvorbis exports
|
|
extern int ov_open_callbacks( void *datasrc, vorbisfile_t *vf, char *initial, long ibytes, ov_callbacks_t callbacks );
|
|
extern long ov_read( vorbisfile_t *vf, char *buffer, int length, int bigendianp, int word, int sgned, int *bitstream );
|
|
extern char *vorbis_comment_query( vorbis_comment_t *vc, char *tag, int count );
|
|
extern vorbis_comment_t *ov_comment( vorbisfile_t *vf, int link );
|
|
extern ogg_int64_t ov_pcm_total( vorbisfile_t *vf, int i );
|
|
extern vorbis_info_t *ov_info( vorbisfile_t *vf, int link );
|
|
extern int ov_raw_seek( vorbisfile_t *vf, ogg_int64_t pos );
|
|
extern ogg_int64_t ov_raw_tell( vorbisfile_t *vf );
|
|
extern int ov_clear( vorbisfile_t *vf);
|
|
|
|
static int ovc_close( void *datasrc ) { return 0; } // close callback generic stub
|
|
|
|
/*
|
|
=================================================================
|
|
|
|
Memory reading funcs
|
|
|
|
=================================================================
|
|
*/
|
|
static size_t ovcm_read( void *ptr, size_t size, size_t nb, void *datasrc )
|
|
{
|
|
ov_decode_t *oggfile = (ov_decode_t *)datasrc;
|
|
size_t remain, length;
|
|
|
|
remain = oggfile->buffsize - oggfile->ind;
|
|
length = size * nb;
|
|
|
|
if( remain < length )
|
|
length = remain - remain % size;
|
|
|
|
Mem_Copy( ptr, oggfile->buffer + oggfile->ind, length );
|
|
oggfile->ind += length;
|
|
|
|
return length / size;
|
|
}
|
|
|
|
static int ovcm_seek( void *datasrc, ogg_int64_t offset, int whence )
|
|
{
|
|
ov_decode_t *oggfile = (ov_decode_t*)datasrc;
|
|
|
|
switch( whence )
|
|
{
|
|
case SEEK_SET:
|
|
break;
|
|
case SEEK_CUR:
|
|
offset += oggfile->ind;
|
|
break;
|
|
case SEEK_END:
|
|
offset += oggfile->buffsize;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
if( offset < 0 || offset > oggfile->buffsize )
|
|
return -1;
|
|
|
|
oggfile->ind = offset;
|
|
return 0;
|
|
}
|
|
|
|
static long ovcm_tell( void *datasrc )
|
|
{
|
|
return ((ov_decode_t *)datasrc)->ind;
|
|
}
|
|
|
|
/*
|
|
=================================================================
|
|
|
|
OGG decompression
|
|
|
|
=================================================================
|
|
*/
|
|
static size_t ovcf_read( void *ptr, size_t size, size_t nb, void *datasrc )
|
|
{
|
|
stream_t *track = (stream_t *)datasrc;
|
|
|
|
if( !size || !nb )
|
|
return 0;
|
|
|
|
return FS_Read( track->file, ptr, size * nb ) / size;
|
|
}
|
|
|
|
static int ovcf_seek( void *datasrc, ogg_int64_t offset, int whence )
|
|
{
|
|
stream_t *track = (stream_t *)datasrc;
|
|
|
|
switch( whence )
|
|
{
|
|
case SEEK_SET:
|
|
FS_Seek( track->file, (int)offset, SEEK_SET );
|
|
break;
|
|
case SEEK_CUR:
|
|
FS_Seek( track->file, (int)offset, SEEK_CUR );
|
|
break;
|
|
case SEEK_END:
|
|
FS_Seek( track->file, (int)offset, SEEK_END );
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static long ovcf_tell( void *datasrc )
|
|
{
|
|
stream_t *track = (stream_t *)datasrc;
|
|
|
|
return FS_Tell( track->file );
|
|
}
|
|
|
|
/*
|
|
=================================================================
|
|
|
|
OGG decompression
|
|
|
|
=================================================================
|
|
*/
|
|
|
|
qboolean Sound_LoadOGG( const char *name, const byte *buffer, size_t filesize )
|
|
{
|
|
vorbisfile_t vf;
|
|
vorbis_info_t *vi;
|
|
vorbis_comment_t *vc;
|
|
ov_decode_t ov_decode;
|
|
ov_callbacks_t ov_callbacks = { ovcm_read, ovcm_seek, ovc_close, ovcm_tell };
|
|
long done = 0, ret;
|
|
int dummy;
|
|
|
|
// load the file
|
|
if( !buffer || filesize <= 0 )
|
|
return false;
|
|
|
|
// Open it with the VorbisFile API
|
|
ov_decode.buffer = (byte *)buffer;
|
|
ov_decode.buffsize = filesize;
|
|
ov_decode.ind = 0;
|
|
|
|
if( ov_open_callbacks( &ov_decode, &vf, NULL, 0, ov_callbacks ) < 0 )
|
|
{
|
|
MsgDev( D_ERROR, "Sound_LoadOGG: couldn't open ogg stream %s\n", name );
|
|
return false;
|
|
}
|
|
|
|
// get the stream information
|
|
vi = ov_info( &vf, -1 );
|
|
|
|
if( vi->channels != 1 && vi->channels != 2 )
|
|
{
|
|
MsgDev( D_ERROR, "Sound_LoadOGG: only mono and stereo OGG files supported (%s)\n", name );
|
|
ov_clear( &vf );
|
|
return false;
|
|
}
|
|
|
|
sound.channels = vi->channels;
|
|
sound.rate = vi->rate;
|
|
sound.width = 2; // always 16-bit PCM
|
|
sound.loopstart = -1;
|
|
sound.size = ov_pcm_total( &vf, -1 ) * vi->channels * 2; // 16 bits => "* 2"
|
|
|
|
if( !sound.size )
|
|
{
|
|
// bad ogg file
|
|
MsgDev( D_ERROR, "Sound_LoadOGG: (%s) is probably corrupted\n", name );
|
|
ov_clear( &vf );
|
|
return false;
|
|
}
|
|
|
|
sound.type = WF_PCMDATA;
|
|
sound.wav = (byte *)Mem_Alloc( Sys.soundpool, sound.size );
|
|
|
|
// decompress ogg into pcm wav format
|
|
while(( ret = ov_read( &vf, &sound.wav[done], (int)(sound.size - done), false, 2, 1, &dummy )) > 0 )
|
|
done += ret;
|
|
sound.samples = done / ( vi->channels * 2 );
|
|
vc = ov_comment( &vf, -1 );
|
|
|
|
if( vc )
|
|
{
|
|
const char *start, *end, *looplength;
|
|
|
|
start = vorbis_comment_query( vc, "LOOP_START", 0 ); // DarkPlaces, and some Japanese app
|
|
if( start )
|
|
{
|
|
end = vorbis_comment_query( vc, "LOOP_END", 0 );
|
|
if( !end ) looplength = vorbis_comment_query( vc, "LOOP_LENGTH", 0 );
|
|
}
|
|
else
|
|
{
|
|
// RPG Maker VX
|
|
start = vorbis_comment_query( vc, "LOOPSTART", 0 );
|
|
if( start )
|
|
{
|
|
looplength = vorbis_comment_query( vc, "LOOPLENGTH", 0 );
|
|
if( !looplength ) end = vorbis_comment_query( vc, "LOOPEND", 0 );
|
|
}
|
|
else
|
|
{
|
|
// Sonic Robo Blast 2
|
|
start = vorbis_comment_query( vc, "LOOPPOINT", 0 );
|
|
}
|
|
}
|
|
|
|
if( start )
|
|
{
|
|
sound.loopstart = (uint)bound( 0, com.atof( start ), sound.samples );
|
|
if( end ) sound.samples = (uint)bound( 0, com.atof( end ), sound.samples );
|
|
else if( looplength )
|
|
{
|
|
size_t length = com.atof( looplength );
|
|
sound.samples = (uint)bound( 0, sound.loopstart + length, sound.samples );
|
|
}
|
|
}
|
|
}
|
|
|
|
// close file
|
|
ov_clear( &vf );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Stream_OpenOGG
|
|
=================
|
|
*/
|
|
stream_t *Stream_OpenOGG( const char *filename )
|
|
{
|
|
vorbisfile_t *vorbisFile;
|
|
vorbis_info_t *vorbisInfo;
|
|
ov_callbacks_t vorbisCallbacks = { ovcf_read, ovcf_seek, ovc_close, ovcf_tell };
|
|
stream_t *stream;
|
|
file_t *file;
|
|
|
|
file = FS_Open( filename, "rb", false );
|
|
if( !file ) return NULL;
|
|
|
|
// at this point we have valid stream
|
|
stream = Mem_Alloc( Sys.soundpool, sizeof( stream_t ));
|
|
stream->file = file;
|
|
|
|
vorbisFile = Mem_Alloc( Sys.soundpool, sizeof( vorbisfile_t ));
|
|
|
|
if( ov_open_callbacks( stream, vorbisFile, NULL, 0, vorbisCallbacks ) < 0 )
|
|
{
|
|
MsgDev( D_ERROR, "Stream_OpenOGG: couldn't open %s\n", filename );
|
|
ov_clear( vorbisFile );
|
|
Mem_Free( vorbisFile );
|
|
Mem_Free( stream );
|
|
FS_Close( file );
|
|
return NULL;
|
|
}
|
|
|
|
vorbisInfo = ov_info( vorbisFile, -1 );
|
|
if( vorbisInfo->channels != 1 && vorbisInfo->channels != 2 )
|
|
{
|
|
MsgDev( D_ERROR, "Stream_OpenOGG: only mono and stereo ogg files supported %s\n", filename );
|
|
ov_clear( vorbisFile );
|
|
Mem_Free( vorbisFile );
|
|
Mem_Free( stream );
|
|
FS_Close( file );
|
|
return NULL;
|
|
}
|
|
|
|
stream->pos = ov_raw_tell( vorbisFile );
|
|
stream->channels = vorbisInfo->channels;
|
|
stream->width = 2; // always 16 bit
|
|
stream->rate = vorbisInfo->rate; // save rate instead of constant width
|
|
stream->ptr = vorbisFile;
|
|
stream->type = WF_OGGDATA;
|
|
|
|
return stream;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Stream_ReadOGG
|
|
|
|
assume stream is valid
|
|
=================
|
|
*/
|
|
long Stream_ReadOGG( stream_t *stream, long bytes, void *buffer )
|
|
{
|
|
// buffer handling
|
|
int bytesRead, bytesLeft;
|
|
int c, dummy;
|
|
char *bufPtr;
|
|
|
|
bytesRead = 0;
|
|
bytesLeft = bytes;
|
|
bufPtr = buffer;
|
|
|
|
// cycle until we have the requested or all available bytes read
|
|
while( 1 )
|
|
{
|
|
// read some bytes from the OGG codec
|
|
c = ov_read(( vorbisfile_t *)stream->ptr, bufPtr, bytesLeft, false, 2, 1, &dummy );
|
|
|
|
// no more bytes are left
|
|
if( c <= 0 ) break;
|
|
|
|
bytesRead += c;
|
|
bytesLeft -= c;
|
|
bufPtr += c;
|
|
|
|
// we have enough bytes
|
|
if( bytesLeft <= 0 )
|
|
break;
|
|
}
|
|
return bytesRead;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Stream_FreeOGG
|
|
|
|
assume stream is valid
|
|
=================
|
|
*/
|
|
void Stream_FreeOGG( stream_t *stream )
|
|
{
|
|
if( stream->ptr )
|
|
{
|
|
ov_clear( stream->ptr );
|
|
Mem_Free( stream->ptr );
|
|
}
|
|
|
|
if( stream->file )
|
|
{
|
|
FS_Close( stream->file );
|
|
}
|
|
|
|
Mem_Free( stream );
|
|
} |