summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio/oal/stream.cpp448
-rw-r--r--src/core/config.h1
2 files changed, 391 insertions, 58 deletions
diff --git a/src/audio/oal/stream.cpp b/src/audio/oal/stream.cpp
index ff3fed68..cdf063fa 100644
--- a/src/audio/oal/stream.cpp
+++ b/src/audio/oal/stream.cpp
@@ -8,10 +8,14 @@
#include <opusfile.h>
#else
#ifdef _WIN32
+#ifdef AUDIO_OAL_USE_SNDFILE
#pragma comment( lib, "libsndfile-1.lib" )
+#endif
#pragma comment( lib, "libmpg123-0.lib" )
#endif
+#ifdef AUDIO_OAL_USE_SNDFILE
#include <sndfile.h>
+#endif
#include <mpg123.h>
#endif
@@ -78,6 +82,315 @@ public:
CSortStereoBuffer SortStereoBuffer;
#ifndef AUDIO_OPUS
+class CImaADPCMDecoder
+{
+ const uint16 StepTable[89] = {
+ 7, 8, 9, 10, 11, 12, 13, 14,
+ 16, 17, 19, 21, 23, 25, 28, 31,
+ 34, 37, 41, 45, 50, 55, 60, 66,
+ 73, 80, 88, 97, 107, 118, 130, 143,
+ 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658,
+ 724, 796, 876, 963, 1060, 1166, 1282, 1411,
+ 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
+ 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
+ 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
+ 32767
+ };
+
+ int16 Sample, StepIndex;
+
+public:
+ CImaADPCMDecoder()
+ {
+ Init(0, 0);
+ }
+
+ void Init(int16 _Sample, int16 _StepIndex)
+ {
+ Sample = _Sample;
+ StepIndex = _StepIndex;
+ }
+
+ void Decode(uint8 *inbuf, int16 *_outbuf, size_t size)
+ {
+ int16* outbuf = _outbuf;
+ for (size_t i = 0; i < size; i++)
+ {
+ *(outbuf++) = DecodeSample(inbuf[i] & 0xF);
+ *(outbuf++) = DecodeSample(inbuf[i] >> 4);
+ }
+ }
+
+ int16 DecodeSample(uint8 adpcm)
+ {
+ uint16 step = StepTable[StepIndex];
+
+ if (adpcm & 4)
+ StepIndex += ((adpcm & 3) + 1) * 2;
+ else
+ StepIndex--;
+
+ StepIndex = clamp(StepIndex, 0, 88);
+
+ int delta = step >> 3;
+ if (adpcm & 1) delta += step >> 2;
+ if (adpcm & 2) delta += step >> 1;
+ if (adpcm & 4) delta += step;
+ if (adpcm & 8) delta = -delta;
+
+ int newSample = Sample + delta;
+ Sample = clamp(newSample, -32768, 32767);
+ return Sample;
+ }
+};
+
+class CWavFile : public IDecoder
+{
+ enum
+ {
+ WAVEFMT_PCM = 1,
+ WAVEFMT_IMA_ADPCM = 0x11,
+ WAVEFMT_XBOX_ADPCM = 0x69,
+ };
+
+ struct tDataHeader
+ {
+ uint32 ID;
+ uint32 Size;
+ };
+
+ struct tFormatHeader
+ {
+ uint16 AudioFormat;
+ uint16 NumChannels;
+ uint32 SampleRate;
+ uint32 ByteRate;
+ uint16 BlockAlign;
+ uint16 BitsPerSample;
+ uint16 extra[2]; // adpcm only
+
+ tFormatHeader() { memset(this, 0, sizeof(*this)); }
+ };
+
+ FILE *m_pFile;
+ bool m_bIsOpen;
+
+ tFormatHeader m_FormatHeader;
+
+ uint32 m_DataStartOffset; // TODO: 64 bit?
+ uint32 m_nSampleCount;
+ uint32 m_nSamplesPerBlock;
+
+ // ADPCM things
+ uint8 *m_pAdpcmBuffer;
+ int16 **m_ppPcmBuffers;
+ CImaADPCMDecoder *m_pAdpcmDecoders;
+
+ void Close()
+ {
+ if (m_pFile) {
+ fclose(m_pFile);
+ m_pFile = nil;
+ }
+ delete[] m_pAdpcmBuffer;
+ delete[] m_ppPcmBuffers;
+ delete[] m_pAdpcmDecoders;
+ }
+
+ uint32 GetCurrentSample() const
+ {
+ // TODO: 64 bit?
+ uint32 FilePos = ftell(m_pFile);
+ if (FilePos <= m_DataStartOffset)
+ return 0;
+ return (FilePos - m_DataStartOffset) / m_FormatHeader.BlockAlign * m_nSamplesPerBlock;
+ }
+
+public:
+ CWavFile(const char* path) : m_bIsOpen(false), m_DataStartOffset(0), m_nSampleCount(0), m_nSamplesPerBlock(0), m_pAdpcmBuffer(nil), m_ppPcmBuffers(nil), m_pAdpcmDecoders(nil)
+ {
+ m_pFile = fopen(path, "rb");
+ if (!m_pFile) return;
+
+#define CLOSE_ON_ERROR(op)\
+ if (op) { \
+ Close(); \
+ return; \
+ }
+
+ tDataHeader DataHeader;
+
+ CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0);
+ CLOSE_ON_ERROR(DataHeader.ID != 'FFIR');
+
+ // TODO? validate filesizes
+
+ int WAVE;
+ CLOSE_ON_ERROR(fread(&WAVE, 4, 1, m_pFile) == 0);
+ CLOSE_ON_ERROR(WAVE != 'EVAW')
+ CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0);
+ CLOSE_ON_ERROR(DataHeader.ID != ' tmf');
+
+ CLOSE_ON_ERROR(fread(&m_FormatHeader, Min(DataHeader.Size, sizeof(tFormatHeader)), 1, m_pFile) == 0);
+ CLOSE_ON_ERROR(DataHeader.Size > sizeof(tFormatHeader));
+
+ switch (m_FormatHeader.AudioFormat)
+ {
+ case WAVEFMT_XBOX_ADPCM:
+ m_FormatHeader.AudioFormat = WAVEFMT_IMA_ADPCM;
+ case WAVEFMT_IMA_ADPCM:
+ m_nSamplesPerBlock = (m_FormatHeader.BlockAlign / m_FormatHeader.NumChannels - 4) * 2 + 1;
+ m_pAdpcmBuffer = new uint8[m_FormatHeader.BlockAlign];
+ m_ppPcmBuffers = new int16*[m_FormatHeader.NumChannels];
+ m_pAdpcmDecoders = new CImaADPCMDecoder[m_FormatHeader.NumChannels];
+ break;
+ case WAVEFMT_PCM:
+ m_nSamplesPerBlock = 1;
+ if (m_FormatHeader.BitsPerSample != 16)
+ {
+ debug("Unsupported PCM (%d bits), only signed 16-bit is supported (%s)\n", m_FormatHeader.BitsPerSample, path);
+ Close();
+ return;
+ }
+ break;
+ default:
+ debug("Unsupported wav format 0x%x (%s)\n", m_FormatHeader.AudioFormat, path);
+ Close();
+ return;
+ }
+
+ while (true) {
+ CLOSE_ON_ERROR(fread(&DataHeader, sizeof(DataHeader), 1, m_pFile) == 0);
+ if (DataHeader.ID == 'atad')
+ break;
+ fseek(m_pFile, DataHeader.Size, SEEK_CUR);
+ // TODO? validate data size
+ // maybe check if there no extreme custom headers that might break this
+ }
+
+ m_DataStartOffset = ftell(m_pFile);
+ m_nSampleCount = DataHeader.Size / m_FormatHeader.BlockAlign * m_nSamplesPerBlock;
+
+ m_bIsOpen = true;
+#undef CLOSE_ON_ERROR
+ }
+
+ ~CWavFile()
+ {
+ Close();
+ }
+
+ bool IsOpened()
+ {
+ return m_bIsOpen;
+ }
+
+ uint32 GetSampleSize()
+ {
+ return sizeof(uint16);
+ }
+
+ uint32 GetSampleCount()
+ {
+ return m_nSampleCount;
+ }
+
+ uint32 GetSampleRate()
+ {
+ return m_FormatHeader.SampleRate;
+ }
+
+ uint32 GetChannels()
+ {
+ return m_FormatHeader.NumChannels;
+ }
+
+ void Seek(uint32 milliseconds)
+ {
+ if (!IsOpened()) return;
+ fseek(m_pFile, m_DataStartOffset + ms2samples(milliseconds) / m_nSamplesPerBlock * m_FormatHeader.BlockAlign, SEEK_SET);
+ }
+
+ uint32 Tell()
+ {
+ if (!IsOpened()) return 0;
+ return samples2ms(GetCurrentSample());
+ }
+
+#define SAMPLES_IN_LINE (8)
+
+ uint32 Decode(void* buffer)
+ {
+ if (!IsOpened()) return 0;
+
+ if (m_FormatHeader.AudioFormat == WAVEFMT_PCM)
+ {
+ // just read the file and sort the samples
+ uint32 size = fread(buffer, 1, GetBufferSize(), m_pFile);
+ if (m_FormatHeader.NumChannels == 2)
+ SortStereoBuffer.SortStereo(buffer, size);
+ return size;
+ }
+ else if (m_FormatHeader.AudioFormat == WAVEFMT_IMA_ADPCM)
+ {
+ // trim the buffer size if we're at the end of our file
+ uint32 nMaxSamples = GetBufferSamples() / m_FormatHeader.NumChannels;
+ uint32 nSamplesLeft = m_nSampleCount - GetCurrentSample();
+ nMaxSamples = Min(nMaxSamples, nSamplesLeft);
+
+ // align sample count to our block
+ nMaxSamples = nMaxSamples / m_nSamplesPerBlock * m_nSamplesPerBlock;
+
+ // count the size of output buffer
+ uint32 OutBufSizePerChannel = nMaxSamples * GetSampleSize();
+ uint32 OutBufSize = OutBufSizePerChannel * m_FormatHeader.NumChannels;
+
+ // calculate the pointers to individual channel buffers
+ for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++)
+ m_ppPcmBuffers[i] = (int16*)((int8*)buffer + OutBufSizePerChannel * i);
+
+ uint32 samplesRead = 0;
+ while (samplesRead < nMaxSamples)
+ {
+ // read the file
+ uint8 *pAdpcmBuf = m_pAdpcmBuffer;
+ if (fread(m_pAdpcmBuffer, 1, m_FormatHeader.BlockAlign, m_pFile) == 0)
+ return 0;
+
+ // get the first sample in adpcm block and initialise the decoder(s)
+ for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++)
+ {
+ int16 Sample = *(int16*)pAdpcmBuf;
+ pAdpcmBuf += sizeof(int16);
+ int16 Step = *(int16*)pAdpcmBuf;
+ pAdpcmBuf += sizeof(int16);
+ m_pAdpcmDecoders[i].Init(Sample, Step);
+ *(m_ppPcmBuffers[i]) = Sample;
+ m_ppPcmBuffers[i]++;
+ }
+ samplesRead++;
+
+ // decode the rest of the block
+ for (uint32 s = 1; s < m_nSamplesPerBlock; s += SAMPLES_IN_LINE)
+ {
+ for (uint32 i = 0; i < m_FormatHeader.NumChannels; i++)
+ {
+ m_pAdpcmDecoders[i].Decode(pAdpcmBuf, m_ppPcmBuffers[i], SAMPLES_IN_LINE / 2);
+ pAdpcmBuf += SAMPLES_IN_LINE / 2;
+ m_ppPcmBuffers[i] += SAMPLES_IN_LINE;
+ }
+ samplesRead += SAMPLES_IN_LINE;
+ }
+ }
+ return OutBufSize;
+ }
+ return 0;
+ }
+};
+
+#ifdef AUDIO_OAL_USE_SNDFILE
class CSndFile : public IDecoder
{
SNDFILE *m_pfSound;
@@ -146,6 +459,7 @@ public:
return size;
}
};
+#endif
#ifdef _WIN32
// fuzzy seek eliminates stutter when playing ADF but spams errors a lot (nothing breaks though)
@@ -287,7 +601,7 @@ public:
static short quantize(double sample)
{
int a = int(sample + 0.5);
- return short(clamp(int(sample + 0.5), -32768, 32767));
+ return short(clamp(a, -32768, 32767));
}
void Decode(void* _inbuf, int16* _outbuf, size_t size)
@@ -331,64 +645,68 @@ public:
class CVbFile : public IDecoder
{
- FILE* pFile;
- size_t m_FileSize;
- size_t m_nNumberOfBlocks;
- CVagDecoder* decoders;
+ FILE *m_pFile;
+ CVagDecoder *m_pVagDecoders;
+
+ size_t m_FileSize;
+ size_t m_nNumberOfBlocks;
- uint32 m_nSampleRate;
- uint8 m_nChannels;
- bool m_bBlockRead;
- uint16 m_LineInBlock;
- size_t m_CurrentBlock;
+ uint32 m_nSampleRate;
+ uint8 m_nChannels;
+ bool m_bBlockRead;
+ uint16 m_LineInBlock;
+ size_t m_CurrentBlock;
- uint8** ppTempBuffers;
+ uint8 **m_ppVagBuffers; // buffers that cache actual ADPCM file data
+ int16 **m_ppPcmBuffers;
void ReadBlock(int32 block = -1)
{
// just read next block if -1
if (block != -1)
- fseek(pFile, block * m_nChannels * VB_BLOCK_SIZE, SEEK_SET);
+ fseek(m_pFile, block * m_nChannels * VB_BLOCK_SIZE, SEEK_SET);
for (int i = 0; i < m_nChannels; i++)
- fread(ppTempBuffers[i], VB_BLOCK_SIZE, 1, pFile);
+ fread(m_ppVagBuffers[i], VB_BLOCK_SIZE, 1, m_pFile);
m_bBlockRead = true;
}
public:
- CVbFile(const char* path, uint32 nSampleRate = 32000, uint8 nChannels = 2) : m_nSampleRate(nSampleRate), m_nChannels(nChannels)
- {
- pFile = fopen(path, "rb");
- if (pFile) {
- fseek(pFile, 0, SEEK_END);
- m_FileSize = ftell(pFile);
- fseek(pFile, 0, SEEK_SET);
- m_nNumberOfBlocks = m_FileSize / (nChannels * VB_BLOCK_SIZE);
- decoders = new CVagDecoder[nChannels];
- m_CurrentBlock = 0;
- m_LineInBlock = 0;
- m_bBlockRead = false;
- ppTempBuffers = new uint8 * [nChannels];
- for (uint8 i = 0; i < nChannels; i++)
- ppTempBuffers[i] = new uint8[VB_BLOCK_SIZE];
- }
+ CVbFile(const char* path, uint32 nSampleRate = 32000, uint8 nChannels = 2) : m_nSampleRate(nSampleRate), m_nChannels(nChannels), m_pVagDecoders(nil), m_ppVagBuffers(nil), m_ppPcmBuffers(nil),
+ m_FileSize(0), m_nNumberOfBlocks(0), m_bBlockRead(false), m_LineInBlock(0), m_CurrentBlock(0)
+ {
+ m_pFile = fopen(path, "rb");
+ if (!m_pFile) return;
+
+ fseek(m_pFile, 0, SEEK_END);
+ m_FileSize = ftell(m_pFile);
+ fseek(m_pFile, 0, SEEK_SET);
+
+ m_nNumberOfBlocks = m_FileSize / (nChannels * VB_BLOCK_SIZE);
+ m_pVagDecoders = new CVagDecoder[nChannels];
+ m_ppVagBuffers = new uint8*[nChannels];
+ m_ppPcmBuffers = new int16*[nChannels];
+ for (uint8 i = 0; i < nChannels; i++)
+ m_ppVagBuffers[i] = new uint8[VB_BLOCK_SIZE];
}
~CVbFile()
{
- if (pFile)
+ if (m_pFile)
{
- fclose(pFile);
- delete decoders;
+ fclose(m_pFile);
+
+ delete[] m_pVagDecoders;
for (int i = 0; i < m_nChannels; i++)
- delete ppTempBuffers[i];
- delete ppTempBuffers;
+ delete[] m_ppVagBuffers[i];
+ delete[] m_ppVagBuffers;
+ delete[] m_ppPcmBuffers;
}
}
bool IsOpened()
{
- return pFile != nil;
+ return m_pFile != nil;
}
uint32 GetSampleSize()
@@ -416,15 +734,18 @@ public:
{
if (!IsOpened()) return;
uint32 samples = ms2samples(milliseconds);
- int32 block = samples / NUM_VAG_SAMPLES_IN_BLOCK;
+
+ // find the block of our sample
+ uint32 block = samples / NUM_VAG_SAMPLES_IN_BLOCK;
if (block > m_nNumberOfBlocks)
{
samples = 0;
block = 0;
}
if (block != m_CurrentBlock)
- ReadBlock(block);
+ m_bBlockRead = false;
+ // find a line of our sample within our block
uint32 remainingSamples = samples - block * NUM_VAG_SAMPLES_IN_BLOCK;
uint32 newLine = remainingSamples / VAG_SAMPLES_IN_LINE / VAG_LINE_SIZE;
@@ -432,8 +753,8 @@ public:
{
m_CurrentBlock = block;
m_LineInBlock = newLine;
- for (int i = 0; i < GetChannels(); i++)
- decoders[i].ResetState();
+ for (uint32 i = 0; i < GetChannels(); i++)
+ m_pVagDecoders[i].ResetState();
}
}
@@ -449,41 +770,45 @@ public:
{
if (!IsOpened()) return 0;
+ if (m_CurrentBlock >= m_nNumberOfBlocks) return 0;
+
+ // cache current ADPCM block
if (!m_bBlockRead)
ReadBlock(m_CurrentBlock);
- if (m_CurrentBlock == m_nNumberOfBlocks) return 0;
- int size = 0;
-
- int numberOfRequiredLines = GetBufferSamples() / GetChannels() / VAG_SAMPLES_IN_LINE;
+ // trim the buffer size if we're at the end of our file
+ int numberOfRequiredLines = GetBufferSamples() / m_nChannels / VAG_SAMPLES_IN_LINE;
int numberOfRemainingLines = (m_nNumberOfBlocks - m_CurrentBlock) * NUM_VAG_LINES_IN_BLOCK - m_LineInBlock;
int bufSizePerChannel = Min(numberOfRequiredLines, numberOfRemainingLines) * VAG_SAMPLES_IN_LINE * GetSampleSize();
- if (numberOfRequiredLines > numberOfRemainingLines)
- numberOfRemainingLines = numberOfRemainingLines;
-
- int16* buffers[2] = { (int16*)buffer, &((int16*)buffer)[bufSizePerChannel / GetSampleSize()] };
+ // calculate the pointers to individual channel buffers
+ for (uint32 i = 0; i < m_nChannels; i++)
+ m_ppPcmBuffers[i] = (int16*)((int8*)buffer + bufSizePerChannel * i);
+ int size = 0;
while (size < bufSizePerChannel)
{
- for (int i = 0; i < GetChannels(); i++)
+ // decode the VAG lines
+ for (uint32 i = 0; i < m_nChannels; i++)
{
- decoders[i].Decode(ppTempBuffers[i] + m_LineInBlock * VAG_LINE_SIZE, buffers[i], VAG_LINE_SIZE);
- buffers[i] += VAG_SAMPLES_IN_LINE;
+ m_pVagDecoders[i].Decode(m_ppVagBuffers[i] + m_LineInBlock * VAG_LINE_SIZE, m_ppPcmBuffers[i], VAG_LINE_SIZE);
+ m_ppPcmBuffers[i] += VAG_SAMPLES_IN_LINE;
}
size += VAG_SAMPLES_IN_LINE * GetSampleSize();
m_LineInBlock++;
+
+ // block is over, read the next block
if (m_LineInBlock >= NUM_VAG_LINES_IN_BLOCK)
{
m_CurrentBlock++;
- if (m_CurrentBlock >= m_nNumberOfBlocks)
+ if (m_CurrentBlock >= m_nNumberOfBlocks) // end of file
break;
m_LineInBlock = 0;
ReadBlock();
}
}
- return bufSizePerChannel * GetChannels();
+ return bufSizePerChannel * m_nChannels;
}
};
#else
@@ -676,7 +1001,11 @@ CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBU
if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".mp3")], ".mp3"))
m_pSoundFile = new CMP3File(m_aFilename);
else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".wav")], ".wav"))
+#ifdef AUDIO_OAL_USE_SNDFILE
m_pSoundFile = new CSndFile(m_aFilename);
+#else
+ m_pSoundFile = new CWavFile(m_aFilename);
+#endif
else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".adf")], ".adf"))
m_pSoundFile = new CADFFile(m_aFilename);
else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".vb")], ".VB"))
@@ -979,12 +1308,15 @@ void CStream::Update()
// Relying a lot on left buffer states in here
- //alSourcef(m_pAlSources[0], AL_ROLLOFF_FACTOR, 0.0f);
- alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState[0]);
- alGetSourcei(m_pAlSources[0], AL_BUFFERS_PROCESSED, &buffersProcessed[0]);
- //alSourcef(m_pAlSources[1], AL_ROLLOFF_FACTOR, 0.0f);
- alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState[1]);
- alGetSourcei(m_pAlSources[1], AL_BUFFERS_PROCESSED, &buffersProcessed[1]);
+ do
+ {
+ //alSourcef(m_pAlSources[0], AL_ROLLOFF_FACTOR, 0.0f);
+ alGetSourcei(m_pAlSources[0], AL_SOURCE_STATE, &sourceState[0]);
+ alGetSourcei(m_pAlSources[0], AL_BUFFERS_PROCESSED, &buffersProcessed[0]);
+ //alSourcef(m_pAlSources[1], AL_ROLLOFF_FACTOR, 0.0f);
+ alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState[1]);
+ alGetSourcei(m_pAlSources[1], AL_BUFFERS_PROCESSED, &buffersProcessed[1]);
+ } while (buffersProcessed[0] != buffersProcessed[1]);
ALint looping = AL_FALSE;
alGetSourcei(m_pAlSources[0], AL_LOOPING, &looping);
diff --git a/src/core/config.h b/src/core/config.h
index 8fd3bc1c..139feddf 100644
--- a/src/core/config.h
+++ b/src/core/config.h
@@ -360,6 +360,7 @@ static_assert(false, "SUPPORT_XBOX_SCRIPT and SUPPORT_MOBILE_SCRIPT are mutually
// Audio
#define AUDIO_CACHE // cache sound lengths to speed up the cold boot
//#define PS2_AUDIO // changes audio paths for cutscenes and radio to PS2 paths, needs vbdec to support VB with MSS
+//#define AUDIO_OAL_USE_SNDFILE // use libsndfile to decode WAVs instead of our internal decoder
#ifdef LIBRW