Serious-Engine/Sources/Engine/Sound/SoundDecoder.cpp

608 lines
17 KiB
C++

/* Copyright (c) 2002-2012 Croteam Ltd.
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
#include "Engine/StdH.h"
#include <Engine/Sound/SoundDecoder.h>
#include <Engine/Base/Stream.h>
#include <Engine/Base/Console.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Base/FileName.h>
#include <Engine/Base/Unzip.h>
#include <Engine/Base/Translation.h>
#include <Engine/Math/Functions.h>
#include <Engine/Base/DynamicLoader.h>
// generic function called if a dll function is not found
static void FailFunction_t(const char *strName) {
ThrowF_t(TRANS("Function %s not found."), strName);
}
// ------------------------------------ AMP11
// amp11lib vars
BOOL _bAMP11Enabled = FALSE;
static CDynamicLoader *_hAmp11lib = NULL;
// amp11lib types
typedef signed char ALsint8;
typedef unsigned char ALuint8;
typedef signed short ALsint16;
typedef unsigned short ALuint16;
typedef signed int ALsint32;
typedef unsigned int ALuint32;
typedef signed int ALsize;
typedef int ALbool;
typedef float ALfloat;
#define ALtrue 1
#define ALfalse 0
typedef ALsint32 ALhandle;
// define amp11lib function pointers
#define DLLFUNCTION(dll, output, name, inputs, params, required) \
output (__stdcall *p##name) inputs = NULL;
#include "al_functions.h"
#undef DLLFUNCTION
static void AMP11_SetFunctionPointers_t(void) {
// get amp11lib function pointers
const char *strName;
#ifdef PLATFORM_WIN32
#define DLLFUNCTION(dll, output, name, inputs, params, required) \
strName = "_" #name "@" #params; \
p##name = (output (__stdcall*) inputs) _hAmp11lib->FindSymbol(strName); \
if(p##name == NULL) FailFunction_t(strName);
#else
#define DLLFUNCTION(dll, output, name, inputs, params, required) \
strName = #name; \
p##name = (output (__stdcall*) inputs) _hAmp11lib->FindSymbol(strName); \
if(p##name == NULL) FailFunction_t(strName);
#endif
#include "al_functions.h"
#undef DLLFUNCTION
}
static void AMP11_ClearFunctionPointers(void) {
// clear amp11lib function pointers
#define DLLFUNCTION(dll, output, name, inputs, params, required) p##name = NULL;
#include "al_functions.h"
#undef DLLFUNCTION
}
class CDecodeData_MPEG {
public:
ALhandle mpeg_hMainFile; // mainfile handle if using subfile
ALhandle mpeg_hFile; // file handle
ALhandle mpeg_hDecoder; // the decoder handle
FLOAT mpeg_fSecondsLen; // length of sound in seconds
WAVEFORMATEX mpeg_wfeFormat; // format of sound
};
// ------------------------------------ Ogg Vorbis
#ifdef USE_TREMOR
#include <tremor/ivorbisfile.h> // we define needed stuff ourselves, and ignore the rest
#else
#include <vorbis/vorbisfile.h> // we define needed stuff ourselves, and ignore the rest
#endif
// vorbis vars
BOOL _bOVEnabled = FALSE;
static CDynamicLoader *_hOV = NULL;
class CDecodeData_OGG {
public:
FILE *ogg_fFile; // the stdio file that ogg is in
SLONG ogg_slOffset; // offset where the ogg starts in the file (!=0 for oggs in zip)
SLONG ogg_slSize; // size of ogg in the file (!=filesize for oggs in zip)
OggVorbis_File *ogg_vfVorbisFile; // the decoder file
WAVEFORMATEX ogg_wfeFormat; // format of sound
};
// define vorbis function pointers
#define DLLFUNCTION(dll, output, name, inputs, params, required) \
output (__cdecl *p##name) inputs = NULL;
#include "ov_functions.h"
#undef DLLFUNCTION
static void OV_SetFunctionPointers_t(void) {
const char *strName;
// get vo function pointers
#define DLLFUNCTION(dll, output, name, inputs, params, required) \
strName = #name ; \
p##name = (output (__cdecl *) inputs) _hOV->FindSymbol(strName); \
if(p##name == NULL) FailFunction_t(strName);
#include "ov_functions.h"
#undef DLLFUNCTION
}
static void OV_ClearFunctionPointers(void) {
// clear vo function pointers
#define DLLFUNCTION(dll, output, name, inputs, params, required) p##name = NULL;
#include "ov_functions.h"
#undef DLLFUNCTION
}
// ogg file reading callbacks
//
static size_t ogg_read_func (void *ptr, size_t size, size_t nmemb, void *datasource)
{
CDecodeData_OGG *pogg = (CDecodeData_OGG *)datasource;
// calculate how much can be read at most
SLONG slToRead = size*nmemb;
SLONG slCurrentPos = ftell(pogg->ogg_fFile)-pogg->ogg_slOffset;
SLONG slSizeLeft = ClampDn(pogg->ogg_slSize-slCurrentPos, 0L);
slToRead = ClampUp(slToRead, slSizeLeft);
// rounded down to the block size
slToRead/=size;
slToRead*=size;
// if there is nothing to read
if (slToRead<=0) {
return 0;
}
return fread(ptr, size, slToRead/size, pogg->ogg_fFile);
}
static int ogg_seek_func (void *datasource, ogg_int64_t offset, int whence)
{
return -1;
/* !!!! seeking is evil with vorbisfile 1.0RC2
CDecodeData_OGG *pogg = (CDecodeData_OGG *)datasource;
SLONG slCurrentPos = ftell(pogg->ogg_fFile)-pogg->ogg_slOffset;
if (whence==SEEK_CUR) {
return fseek(pogg->ogg_fFile, offset, SEEK_CUR);
} else if (whence==SEEK_END) {
return fseek(pogg->ogg_fFile, pogg->ogg_slOffset+pogg->ogg_slSize-offset, SEEK_SET);
} else {
ASSERT(whence==SEEK_SET);
return fseek(pogg->ogg_fFile, pogg->ogg_slOffset+offset, SEEK_SET);
}
*/
}
static int ogg_close_func (void *datasource)
{
return 0;
/* !!!! closing is evil with vorbisfile 1.0RC2
CDecodeData_OGG *pogg = (CDecodeData_OGG *)datasource;
fclose(pogg->ogg_fFile);
*/
}
static long ogg_tell_func (void *datasource)
{
return -1;
/* !!!! seeking is evil with vorbisfile 1.0RC2
CDecodeData_OGG *pogg = (CDecodeData_OGG *)datasource;
ftell(pogg->ogg_fFile)-pogg->ogg_slOffset;
*/
}
static ov_callbacks ovcCallbacks = {
ogg_read_func,
ogg_seek_func,
ogg_close_func,
ogg_tell_func,
};
// initialize/end the decoding support engine(s)
void CSoundDecoder::InitPlugins(void)
{
try {
// load vorbis
if (_hOV==NULL) {
#if ((defined PLATFORM_WIN32) && (defined NDEBUG))
#define VORBISLIB "vorbisfile_d"
#else
#ifdef USE_TREMOR
#define VORBISLIB "vorbisidec"
#else
#define VORBISLIB "vorbisfile"
#endif
#endif
_hOV = CDynamicLoader::GetInstance(VORBISLIB);
if( _hOV->GetError() != NULL) {
ThrowF_t(TRANS("Cannot load " VORBISLIB " shared library: %s."), _hOV->GetError());
}
}
// prepare function pointers
OV_SetFunctionPointers_t();
// if all successful, enable mpx playing
_bOVEnabled = TRUE;
CPrintF(TRANSV(" " VORBISLIB " shared library loaded, ogg playing enabled\n"));
} catch (char *strError) { // !!! FIXME: should be const char* ?
CPrintF(TRANSV("OGG playing disabled: %s\n"), strError);
}
try {
// load amp11lib
if (_hAmp11lib==NULL) {
_hAmp11lib = CDynamicLoader::GetInstance("amp11lib");
if( _hAmp11lib->GetError() != NULL) {
ThrowF_t(TRANS("Cannot load amp11lib shared library: %s"), _hAmp11lib->GetError());
}
}
// prepare function pointers
AMP11_SetFunctionPointers_t();
// initialize amp11lib before calling any of its functions
palInitLibrary();
// if all successful, enable mpx playing
_bAMP11Enabled = TRUE;
CPrintF(TRANSV(" amp11lib shared library loaded, mpx playing enabled\n"));
} catch (char *strError) { // !!! FIXME: should be const char* ?
CPrintF(TRANSV("MPX playing disabled: %s\n"), strError);
}
}
void CSoundDecoder::EndPlugins(void)
{
// cleanup amp11lib when not needed anymore
if (_bAMP11Enabled) {
palEndLibrary();
AMP11_ClearFunctionPointers();
delete _hAmp11lib;
_hAmp11lib = NULL;
_bAMP11Enabled = FALSE;
}
// cleanup vorbis when not needed anymore
if (_bOVEnabled) {
OV_ClearFunctionPointers();
delete _hOV;
_hOV = NULL;
_bOVEnabled = FALSE;
}
}
// decoder that streams from file
CSoundDecoder::CSoundDecoder(const CTFileName &fnm)
{
sdc_pogg = NULL;
sdc_pmpeg = NULL;
CTFileName fnmExpanded;
INDEX iFileType = ExpandFilePath(EFP_READ, fnm, fnmExpanded);
// if ogg
if (fnmExpanded.FileExt()==".ogg") {
if (!_bOVEnabled) {
return;
}
sdc_pogg = new CDecodeData_OGG;
sdc_pogg->ogg_fFile = NULL;
sdc_pogg->ogg_vfVorbisFile = NULL;
sdc_pogg->ogg_slOffset = 0;
sdc_pogg->ogg_slSize = 0;
INDEX iZipHandle = 0;
try {
// if in zip
if (iFileType==EFP_BASEZIP || iFileType==EFP_MODZIP) {
// open it
iZipHandle = UNZIPOpen_t(fnmExpanded);
CTFileName fnmZip;
SLONG slOffset;
SLONG slSizeCompressed;
SLONG slSizeUncompressed;
BOOL bCompressed;
UNZIPGetFileInfo(iZipHandle, fnmZip, slOffset, slSizeCompressed, slSizeUncompressed, bCompressed);
// if compressed
if (bCompressed) {
ThrowF_t(TRANS("encoded audio in archives must not be compressed!\n"));
}
// open ogg file
sdc_pogg->ogg_fFile = fopen(fnmZip, "rb");
// if error
if (sdc_pogg->ogg_fFile==0) {
ThrowF_t(TRANS("cannot open archive '%s'"), (const char*)fnmZip);
}
// remember offset and size
sdc_pogg->ogg_slOffset = slOffset;
sdc_pogg->ogg_slSize = slSizeUncompressed;
fseek(sdc_pogg->ogg_fFile, slOffset, SEEK_SET);
// if not in zip
} else if (iFileType==EFP_FILE) {
// open ogg file
sdc_pogg->ogg_fFile = fopen(fnmExpanded, "rb");
// if error
if (sdc_pogg->ogg_fFile==0) {
ThrowF_t(TRANS("cannot open encoded audio file"));
}
// remember offset and size
sdc_pogg->ogg_slOffset = 0;
fseek(sdc_pogg->ogg_fFile, 0, SEEK_END);
sdc_pogg->ogg_slSize = ftell(sdc_pogg->ogg_fFile);
fseek(sdc_pogg->ogg_fFile, 0, SEEK_SET);
// if not found
} else {
ThrowF_t(TRANS("file not found"));
}
// initialize decoder
sdc_pogg->ogg_vfVorbisFile = new OggVorbis_File;
int iRes = pov_open_callbacks(sdc_pogg, sdc_pogg->ogg_vfVorbisFile, NULL, 0, ovcCallbacks);
// if error
if (iRes!=0) {
ThrowF_t(TRANS("cannot open ogg decoder"));
}
// get info on the file
vorbis_info *pvi = pov_info(sdc_pogg->ogg_vfVorbisFile, -1);
// remember it's format
WAVEFORMATEX form;
form.wFormatTag=WAVE_FORMAT_PCM;
form.nChannels=pvi->channels;
form.nSamplesPerSec=pvi->rate;
form.wBitsPerSample=16;
form.nBlockAlign=form.nChannels*form.wBitsPerSample/8;
form.nAvgBytesPerSec=form.nSamplesPerSec*form.nBlockAlign;
form.cbSize=0;
// check for stereo
if (pvi->channels!=2) {
ThrowF_t(TRANS("not stereo"));
}
sdc_pogg->ogg_wfeFormat = form;
} catch (char*strError) {
CPrintF(TRANSV("Cannot open encoded audio '%s' for streaming: %s\n"), (const char*)fnm, (const char*)strError);
if (sdc_pogg->ogg_vfVorbisFile!=NULL) {
delete sdc_pogg->ogg_vfVorbisFile;
sdc_pogg->ogg_vfVorbisFile = NULL;
}
if (sdc_pogg->ogg_fFile!=NULL) {
fclose(sdc_pogg->ogg_fFile);
sdc_pogg->ogg_fFile = NULL;
}
if (iZipHandle!=0) {
UNZIPClose(iZipHandle);
}
Clear();
return;
}
if (iZipHandle!=0) {
UNZIPClose(iZipHandle);
}
// if mp3
} else if (fnmExpanded.FileExt()==".mp3") {
if (!_bAMP11Enabled) {
return;
}
sdc_pmpeg = new CDecodeData_MPEG;
sdc_pmpeg->mpeg_hMainFile = 0;
sdc_pmpeg->mpeg_hFile = 0;
sdc_pmpeg->mpeg_hDecoder = 0;
INDEX iZipHandle = 0;
try {
// if in zip
if (iFileType==EFP_BASEZIP || iFileType==EFP_MODZIP) {
// open it
iZipHandle = UNZIPOpen_t(fnmExpanded);
CTFileName fnmZip;
SLONG slOffset;
SLONG slSizeCompressed;
SLONG slSizeUncompressed;
BOOL bCompressed;
UNZIPGetFileInfo(iZipHandle, fnmZip, slOffset, slSizeCompressed, slSizeUncompressed, bCompressed);
// if compressed
if (bCompressed) {
ThrowF_t(TRANS("encoded audio in archives must not be compressed!\n"));
}
// open the zip file
sdc_pmpeg->mpeg_hMainFile = palOpenInputFile(fnmZip);
// if error
if (sdc_pmpeg->mpeg_hMainFile==0) {
ThrowF_t(TRANS("cannot open archive '%s'"), (const char*)fnmZip);
}
// open the subfile
sdc_pmpeg->mpeg_hFile = palOpenSubFile(sdc_pmpeg->mpeg_hMainFile, slOffset, slSizeUncompressed);
// if error
if (sdc_pmpeg->mpeg_hFile==0) {
ThrowF_t(TRANS("cannot open encoded audio file"));
}
// if not in zip
} else if (iFileType==EFP_FILE) {
// open mpx file
sdc_pmpeg->mpeg_hFile = palOpenInputFile(fnmExpanded);
// if error
if (sdc_pmpeg->mpeg_hFile==0) {
ThrowF_t(TRANS("cannot open mpx file"));
}
// if not found
} else {
ThrowF_t(TRANS("file not found"));
}
// get info on the file
int layer, ver, freq, stereo, rate;
if (!palGetMPXHeader(sdc_pmpeg->mpeg_hFile, &layer, &ver, &freq, &stereo, &rate)) {
ThrowF_t(TRANS("not a valid mpeg audio file."));
}
// remember it's format
WAVEFORMATEX form;
form.wFormatTag=WAVE_FORMAT_PCM;
form.nChannels=stereo?2:1;
form.nSamplesPerSec=freq;
form.wBitsPerSample=16;
form.nBlockAlign=form.nChannels*form.wBitsPerSample/8;
form.nAvgBytesPerSec=form.nSamplesPerSec*form.nBlockAlign;
form.cbSize=0;
// check for stereo
if (!stereo) {
ThrowF_t(TRANS("not stereo"));
}
sdc_pmpeg->mpeg_wfeFormat = form;
// initialize decoder
sdc_pmpeg->mpeg_hDecoder = palOpenDecoder(sdc_pmpeg->mpeg_hFile);
// if error
if (sdc_pmpeg->mpeg_hDecoder==0) {
ThrowF_t(TRANS("cannot open mpx decoder"));
}
} catch (char*strError) {
CPrintF(TRANSV("Cannot open mpx '%s' for streaming: %s\n"), (const char*)fnm, (const char*)strError);
if (iZipHandle!=0) {
UNZIPClose(iZipHandle);
}
Clear();
return;
}
if (iZipHandle!=0) {
UNZIPClose(iZipHandle);
}
sdc_pmpeg->mpeg_fSecondsLen = palDecGetLen(sdc_pmpeg->mpeg_hDecoder);
}
}
CSoundDecoder::~CSoundDecoder(void)
{
Clear();
}
void CSoundDecoder::Clear(void)
{
if (sdc_pmpeg!=NULL) {
if (sdc_pmpeg->mpeg_hDecoder!=0) palClose(sdc_pmpeg->mpeg_hDecoder);
if (sdc_pmpeg->mpeg_hFile!=0) palClose(sdc_pmpeg->mpeg_hFile);
if (sdc_pmpeg->mpeg_hMainFile!=0) palClose(sdc_pmpeg->mpeg_hMainFile);
sdc_pmpeg->mpeg_hMainFile = 0;
sdc_pmpeg->mpeg_hFile = 0;
sdc_pmpeg->mpeg_hDecoder = 0;
delete sdc_pmpeg;
sdc_pmpeg = NULL;
} else if (sdc_pogg!=NULL) {
if (sdc_pogg->ogg_vfVorbisFile!=NULL) {
pov_clear(sdc_pogg->ogg_vfVorbisFile);
delete sdc_pogg->ogg_vfVorbisFile;
sdc_pogg->ogg_vfVorbisFile = NULL;
}
if (sdc_pogg->ogg_fFile!=NULL) {
fclose(sdc_pogg->ogg_fFile);
sdc_pogg->ogg_fFile = NULL;
}
delete sdc_pogg;
sdc_pogg = NULL;
}
}
// reset decoder to start of sample
void CSoundDecoder::Reset(void)
{
if (sdc_pmpeg!=NULL) {
palDecSeekAbs(sdc_pmpeg->mpeg_hDecoder, 0.0f);
} else if (sdc_pogg!=NULL) {
// so instead, we reinit
pov_clear(sdc_pogg->ogg_vfVorbisFile);
fseek(sdc_pogg->ogg_fFile, sdc_pogg->ogg_slOffset, SEEK_SET);
pov_open_callbacks(sdc_pogg, sdc_pogg->ogg_vfVorbisFile, NULL, 0, ovcCallbacks);
}
}
BOOL CSoundDecoder::IsOpen(void)
{
if (sdc_pmpeg!=NULL && sdc_pmpeg->mpeg_hDecoder!=0) {
return TRUE;
} else if (sdc_pogg!=NULL && sdc_pogg->ogg_vfVorbisFile!=0) {
return TRUE;
} else {
return FALSE;
}
}
void CSoundDecoder::GetFormat(WAVEFORMATEX &wfe)
{
if (sdc_pmpeg!=NULL) {
wfe = sdc_pmpeg->mpeg_wfeFormat;
} else if (sdc_pogg!=NULL) {
wfe = sdc_pogg->ogg_wfeFormat;
} else {
NOTHING;
}
}
// decode a block of bytes
INDEX CSoundDecoder::Decode(void *pvDestBuffer, INDEX ctBytesToDecode)
{
// if ogg
if (sdc_pogg!=NULL && sdc_pogg->ogg_vfVorbisFile!=0) {
// decode ogg
static int iCurrrentSection = -1; // we don't care about this
char *pch = (char *)pvDestBuffer;
INDEX ctDecoded = 0;
while (ctDecoded<ctBytesToDecode) {
#ifdef USE_TREMOR
long iRes = pov_read(sdc_pogg->ogg_vfVorbisFile, pch, ctBytesToDecode-ctDecoded, &iCurrrentSection);
#else
long iRes = pov_read(sdc_pogg->ogg_vfVorbisFile, pch, ctBytesToDecode-ctDecoded,
0, 2, 1, &iCurrrentSection);
#endif
if (iRes<=0) {
return ctDecoded;
}
ctDecoded+=iRes;
pch+=iRes;
}
return ctDecoded;
// if mpeg
} else if (sdc_pmpeg!=NULL && sdc_pmpeg->mpeg_hDecoder!=0) {
// decode mpeg
return palRead(sdc_pmpeg->mpeg_hDecoder, pvDestBuffer, ctBytesToDecode);
// if no decoder
} else {
// play all zeroes
memset(pvDestBuffer, 0, ctBytesToDecode);
return ctBytesToDecode;
}
}