This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/launch/soundlib/snd_ogg.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 );
}