You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Added an audio format for Opus, which also required a resampler to make sure that all imported sounds are converted to a compatible sample rate. Added the speex resampler from the opus tools package to the third party source, including built libraries for windows and mac. Changed FVorbisAudioInfo so that it inherits from an interface for any kind of compressed audio, which can be used everywhere instead of being wrapped in #WITH_VORBIS. Added FOpusAudioInfo to decompress Opus data, not sure at this point whether it's only going to be used for streaming audio but works for non-streamed playback. [CL 2056352 by Matthew Griffin in Main branch]
518 lines
16 KiB
C++
518 lines
16 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Core.h"
|
|
#include "ModuleInterface.h"
|
|
#include "ModuleManager.h"
|
|
#include "TargetPlatform.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogAudioFormatADPCM, Log, All);
|
|
|
|
#define UE_MAKEFOURCC(ch0, ch1, ch2, ch3)\
|
|
((uint32)(uint8)(ch0) | ((uint32)(uint8)(ch1) << 8) |\
|
|
((uint32)(uint8)(ch2) << 16) | ((uint32)(uint8)(ch3) << 24 ))
|
|
|
|
#define NUM_ADAPTATION_TABLE 16
|
|
#define NUM_ADAPTATION_COEFF 7
|
|
|
|
#define WAVE_FORMAT_LPCM 1
|
|
#define WAVE_FORMAT_ADPCM 2
|
|
|
|
static FName NAME_ADPCM(TEXT("ADPCM"));
|
|
|
|
namespace
|
|
{
|
|
struct RiffDataChunk
|
|
{
|
|
uint32 ID;
|
|
uint32 DataSize;
|
|
uint8* Data;
|
|
};
|
|
|
|
#if PLATFORM_SUPPORTS_PRAGMA_PACK
|
|
#pragma pack(push, 2)
|
|
#endif
|
|
struct WaveFormatHeader
|
|
{
|
|
uint16 wFormatTag; // Format type: 1 = PCM, 2 = ADPCM
|
|
uint16 nChannels; // Number of channels (i.e. mono, stereo...).
|
|
uint32 nSamplesPerSec; // Sample rate. 44100 or 22050 or 11025 Hz.
|
|
uint32 nAvgBytesPerSec; // For buffer estimation = sample rate * BlockAlign.
|
|
uint16 nBlockAlign; // Block size of data = Channels times BYTES per sample.
|
|
uint16 wBitsPerSample; // Number of bits per sample of mono data.
|
|
uint16 cbSize; // The count in bytes of the size of extra information (after cbSize).
|
|
};
|
|
#if PLATFORM_SUPPORTS_PRAGMA_PACK
|
|
#pragma pack(pop)
|
|
#endif
|
|
|
|
template <uint32 N>
|
|
void GenerateWaveFile(RiffDataChunk(& RiffDataChunks)[N], TArray<uint8>& CompressedDataStore)
|
|
{
|
|
// 'WAVE'
|
|
uint32 RiffDataSize = sizeof(uint32);
|
|
|
|
// Determine the size of the wave file to be generated
|
|
for (uint32 Scan = 0; Scan < N; ++Scan)
|
|
{
|
|
const RiffDataChunk& Chunk = RiffDataChunks[Scan];
|
|
RiffDataSize += (sizeof(Chunk.ID) + sizeof(Chunk.DataSize) + Chunk.DataSize);
|
|
}
|
|
|
|
// Allocate space for the output data + 'RIFF' + ChunkSize
|
|
uint32 OutputDataSize = RiffDataSize + sizeof(uint32) + sizeof(uint32);
|
|
|
|
CompressedDataStore.Empty(OutputDataSize);
|
|
FMemoryWriter CompressedData(CompressedDataStore);
|
|
|
|
uint32 rID = UE_MAKEFOURCC('R','I','F','F');
|
|
CompressedData.Serialize(&rID, sizeof(rID));
|
|
CompressedData.Serialize(&RiffDataSize, sizeof(RiffDataSize));
|
|
|
|
rID = UE_MAKEFOURCC('W','A','V','E');
|
|
CompressedData.Serialize(&rID, sizeof(rID));
|
|
|
|
// Write each sub-chunk to the output data
|
|
for (uint32 Scan = 0; Scan < N; ++Scan)
|
|
{
|
|
RiffDataChunk& Chunk = RiffDataChunks[Scan];
|
|
|
|
CompressedData.Serialize(&Chunk.ID, sizeof(Chunk.ID));
|
|
CompressedData.Serialize(&Chunk.DataSize, sizeof(Chunk.DataSize));
|
|
CompressedData.Serialize(Chunk.Data, Chunk.DataSize);
|
|
}
|
|
}
|
|
|
|
template <typename T, uint32 B>
|
|
inline T SignExtend(const T ValueToExtend)
|
|
{
|
|
struct { T ExtendedValue:B; } SignExtender;
|
|
return SignExtender.ExtendedValue = ValueToExtend;
|
|
}
|
|
|
|
template <typename T>
|
|
inline T ReadFromByteStream(const uint8* ByteStream, int32& ReadIndex, bool bLittleEndian = true)
|
|
{
|
|
T ValueRaw = 0;
|
|
|
|
if (bLittleEndian)
|
|
{
|
|
#if PLATFORM_LITTLE_ENDIAN
|
|
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
|
|
#else
|
|
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
|
|
#endif // PLATFORM_LITTLE_ENDIAN
|
|
{
|
|
ValueRaw |= ByteStream[ReadIndex++] << 8 * ByteIndex;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if PLATFORM_LITTLE_ENDIAN
|
|
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
|
|
#else
|
|
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
|
|
#endif // PLATFORM_LITTLE_ENDIAN
|
|
{
|
|
ValueRaw |= ByteStream[ReadIndex++] << 8 * ByteIndex;
|
|
}
|
|
}
|
|
|
|
return ValueRaw;
|
|
}
|
|
|
|
template <typename T>
|
|
inline void WriteToByteStream(T Value, uint8* ByteStream, int32& WriteIndex, bool bLittleEndian = true)
|
|
{
|
|
if (bLittleEndian)
|
|
{
|
|
#if PLATFORM_LITTLE_ENDIAN
|
|
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
|
|
#else
|
|
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
|
|
#endif // PLATFORM_LITTLE_ENDIAN
|
|
{
|
|
ByteStream[WriteIndex++] = (Value >> (8 * ByteIndex)) & 0xFF;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if PLATFORM_LITTLE_ENDIAN
|
|
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
|
|
#else
|
|
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
|
|
#endif // PLATFORM_LITTLE_ENDIAN
|
|
{
|
|
ByteStream[WriteIndex++] = (Value >> (8 * ByteIndex)) & 0xFF;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
inline T ReadFromArray(const T* ElementArray, int32& ReadIndex, int32 NumElements, int32 IndexStride = 1)
|
|
{
|
|
T OutputValue = 0;
|
|
|
|
if (ReadIndex >= 0 && ReadIndex < NumElements)
|
|
{
|
|
OutputValue = ElementArray[ReadIndex];
|
|
ReadIndex += IndexStride;
|
|
}
|
|
|
|
return OutputValue;
|
|
}
|
|
|
|
template <typename T>
|
|
inline T WriteToArray(const T* ElementArray, int32& ReadIndex, int32 NumElements, int32 IndexStride = 1)
|
|
{
|
|
T OutputValue = 0;
|
|
|
|
if (ReadIndex >= 0 && ReadIndex < NumElements)
|
|
{
|
|
OutputValue = ElementArray[ReadIndex];
|
|
ReadIndex += IndexStride;
|
|
}
|
|
|
|
return OutputValue;
|
|
}
|
|
} // end namespace
|
|
|
|
namespace LPCM
|
|
{
|
|
void Encode(const TArray<uint8>& InputPCMData, TArray<uint8>& CompressedDataStore, const FSoundQualityInfo& QualityInfo)
|
|
{
|
|
WaveFormatHeader Format;
|
|
Format.nChannels = static_cast<uint16>(QualityInfo.NumChannels);
|
|
Format.nSamplesPerSec = QualityInfo.SampleRate;
|
|
Format.nBlockAlign = static_cast<uint16>(Format.nChannels * sizeof(int16));
|
|
Format.nAvgBytesPerSec = Format.nBlockAlign * QualityInfo.SampleRate;
|
|
Format.wBitsPerSample = 16;
|
|
Format.wFormatTag = WAVE_FORMAT_LPCM;
|
|
|
|
RiffDataChunk RiffDataChunks[2];
|
|
RiffDataChunks[0].ID = UE_MAKEFOURCC('f','m','t',' ');
|
|
RiffDataChunks[0].DataSize = sizeof(Format);
|
|
RiffDataChunks[0].Data = reinterpret_cast<uint8*>(&Format);
|
|
|
|
RiffDataChunks[1].ID = UE_MAKEFOURCC('d','a','t','a');
|
|
RiffDataChunks[1].DataSize = InputPCMData.Num();
|
|
RiffDataChunks[1].Data = const_cast<uint8*>(InputPCMData.GetTypedData());
|
|
|
|
GenerateWaveFile(RiffDataChunks, CompressedDataStore);
|
|
}
|
|
} // end namespace LPCM
|
|
|
|
namespace ADPCM
|
|
{
|
|
template <typename T>
|
|
static void GetAdaptationTable(T(& OutAdaptationTable)[NUM_ADAPTATION_TABLE])
|
|
{
|
|
// Magic values as specified by standard
|
|
static T AdaptationTable[] =
|
|
{
|
|
230, 230, 230, 230, 307, 409, 512, 614,
|
|
768, 614, 512, 409, 307, 230, 230, 230
|
|
};
|
|
|
|
FMemory::Memcpy(&OutAdaptationTable, AdaptationTable, sizeof(AdaptationTable));
|
|
}
|
|
|
|
template <typename T>
|
|
static void GetAdaptationCoefficients(T(& OutAdaptationCoefficient1)[NUM_ADAPTATION_COEFF], T(& OutAdaptationCoefficient2)[NUM_ADAPTATION_COEFF])
|
|
{
|
|
// Magic values as specified by standard
|
|
static T AdaptationCoefficient1[] =
|
|
{
|
|
256, 512, 0, 192, 240, 460, 392
|
|
};
|
|
static T AdaptationCoefficient2[] =
|
|
{
|
|
0, -256, 0, 64, 0, -208, -232
|
|
};
|
|
|
|
FMemory::Memcpy(&OutAdaptationCoefficient1, AdaptationCoefficient1, sizeof(AdaptationCoefficient1));
|
|
FMemory::Memcpy(&OutAdaptationCoefficient2, AdaptationCoefficient2, sizeof(AdaptationCoefficient2));
|
|
}
|
|
|
|
struct FAdaptationContext
|
|
{
|
|
public:
|
|
// Adaptation constants
|
|
int32 AdaptationTable[NUM_ADAPTATION_TABLE];
|
|
int32 AdaptationCoefficient1[NUM_ADAPTATION_COEFF];
|
|
int32 AdaptationCoefficient2[NUM_ADAPTATION_COEFF];
|
|
|
|
int32 AdaptationDelta;
|
|
int32 Coefficient1;
|
|
int32 Coefficient2;
|
|
int32 Sample1;
|
|
int32 Sample2;
|
|
|
|
FAdaptationContext() :
|
|
AdaptationDelta(0),
|
|
Coefficient1(0),
|
|
Coefficient2(0),
|
|
Sample1(0),
|
|
Sample2(0)
|
|
{
|
|
GetAdaptationTable(AdaptationTable);
|
|
GetAdaptationCoefficients(AdaptationCoefficient1, AdaptationCoefficient2);
|
|
}
|
|
};
|
|
|
|
#if PLATFORM_SUPPORTS_PRAGMA_PACK
|
|
#pragma pack(push, 2)
|
|
#endif
|
|
struct ADPCMFormatHeader
|
|
{
|
|
WaveFormatHeader BaseFormat;
|
|
uint16 wSamplesPerBlock;
|
|
uint16 wNumCoef;
|
|
int16 aCoef[2 * NUM_ADAPTATION_COEFF];
|
|
|
|
ADPCMFormatHeader()
|
|
{
|
|
int16 AdaptationCoefficient1[NUM_ADAPTATION_COEFF];
|
|
int16 AdaptationCoefficient2[NUM_ADAPTATION_COEFF];
|
|
GetAdaptationCoefficients(AdaptationCoefficient1, AdaptationCoefficient2);
|
|
|
|
// Interlace the coefficients as pairs
|
|
for (int32 Coeff = 0, WriteIndex = 0; Coeff < NUM_ADAPTATION_COEFF; Coeff++)
|
|
{
|
|
aCoef[WriteIndex++] = AdaptationCoefficient1[Coeff];
|
|
aCoef[WriteIndex++] = AdaptationCoefficient2[Coeff];
|
|
}
|
|
}
|
|
};
|
|
#if PLATFORM_SUPPORTS_PRAGMA_PACK
|
|
#pragma pack(pop)
|
|
#endif
|
|
|
|
/**
|
|
* Encodes a 16-bit PCM sample to a 4-bit ADPCM sample.
|
|
**/
|
|
uint8 EncodeNibble(FAdaptationContext& Context, int16 NextSample)
|
|
{
|
|
int32 PredictedSample = (Context.Sample1 * Context.Coefficient1 + Context.Sample2 * Context.Coefficient2) / 256;
|
|
int32 ErrorDelta = (NextSample - PredictedSample) / Context.AdaptationDelta;
|
|
ErrorDelta = FMath::Clamp(ErrorDelta, -8, 7);
|
|
|
|
// Predictor must be clamped within the 16-bit range
|
|
PredictedSample += (Context.AdaptationDelta * ErrorDelta);
|
|
PredictedSample = FMath::Clamp(PredictedSample, -32768, 32767);
|
|
|
|
int8 SmallDelta = static_cast<int8>(ErrorDelta);
|
|
uint8 EncodedNibble = reinterpret_cast<uint8&>(SmallDelta) & 0x0F;
|
|
|
|
// Shuffle samples for the next iteration
|
|
Context.Sample2 = Context.Sample1;
|
|
Context.Sample1 = static_cast<int16>(PredictedSample);
|
|
Context.AdaptationDelta = (Context.AdaptationDelta * Context.AdaptationTable[EncodedNibble]) / 256;
|
|
Context.AdaptationDelta = FMath::Max(Context.AdaptationDelta, 16);
|
|
|
|
return EncodedNibble;
|
|
}
|
|
|
|
int32 EncodeBlock(const int16* InputPCMSamples, int32 SampleStride, int32 NumSamples, int32 BlockSize, uint8* EncodedADPCMData)
|
|
{
|
|
FAdaptationContext Context;
|
|
int32 ReadIndex = 0;
|
|
int32 WriteIndex = 0;
|
|
|
|
/* TODO::JTM - Dec 10, 2012 05:30PM - Calculate the optimal starting coefficient */
|
|
uint8 CoefficientIndex = 0;
|
|
Context.AdaptationDelta = Context.AdaptationTable[0];
|
|
Context.Sample1 = ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride);
|
|
Context.Sample2 = ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride);
|
|
Context.Coefficient1 = Context.AdaptationCoefficient1[CoefficientIndex];
|
|
Context.Coefficient2 = Context.AdaptationCoefficient2[CoefficientIndex];
|
|
|
|
// Populate the block preamble
|
|
// [0]: Block Predictor
|
|
// [1-2]: Initial Adaptation Delta
|
|
// [3-4]: First Sample
|
|
// [5-6]: Second Sample
|
|
WriteToByteStream<uint8>(CoefficientIndex, EncodedADPCMData, WriteIndex);
|
|
WriteToByteStream<int16>(Context.AdaptationDelta, EncodedADPCMData, WriteIndex);
|
|
WriteToByteStream<int16>(Context.Sample1, EncodedADPCMData, WriteIndex);
|
|
WriteToByteStream<int16>(Context.Sample2, EncodedADPCMData, WriteIndex);
|
|
|
|
// Process all the nibble pairs after the preamble.
|
|
while (WriteIndex < BlockSize)
|
|
{
|
|
EncodedADPCMData[WriteIndex] = EncodeNibble(Context, ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride)) << 4;
|
|
EncodedADPCMData[WriteIndex++] |= EncodeNibble(Context, ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride));
|
|
}
|
|
|
|
return WriteIndex;
|
|
}
|
|
|
|
void Encode(const TArray<uint8>& InputPCMData, TArray<uint8>& CompressedDataStore, const FSoundQualityInfo& QualityInfo)
|
|
{
|
|
const int32 SourceSampleStride = QualityInfo.NumChannels;
|
|
|
|
// Input source samples are 2-bytes
|
|
const int32 SourceNumSamples = QualityInfo.SampleDataSize / 2;
|
|
const int32 SourceNumSamplesPerChannel = SourceNumSamples / QualityInfo.NumChannels;
|
|
|
|
// Output samples are 4-bits
|
|
const int32 CompressedNumSamplesPerByte = 2;
|
|
const int32 PreambleSamples = 2;
|
|
const int32 BlockSize = 512;
|
|
const int32 PreambleSize = 2 * PreambleSamples + 3;
|
|
const int32 CompressedSamplesPerBlock = (BlockSize - PreambleSize) * CompressedNumSamplesPerByte + PreambleSamples;
|
|
int32 NumBlocksPerChannel = SourceNumSamplesPerChannel / CompressedSamplesPerBlock;
|
|
|
|
// If our storage didn't exactly line up with the number of samples, we need an extra block
|
|
if (NumBlocksPerChannel * CompressedSamplesPerBlock != SourceNumSamplesPerChannel)
|
|
{
|
|
NumBlocksPerChannel++;
|
|
}
|
|
|
|
const uint32 EncodedADPCMDataSize = NumBlocksPerChannel * BlockSize * QualityInfo.NumChannels;
|
|
uint8* EncodedADPCMData = static_cast<uint8*>(FMemory::Malloc(EncodedADPCMDataSize));
|
|
FMemory::Memzero(EncodedADPCMData, EncodedADPCMDataSize);
|
|
|
|
const int16* InputPCMSamples = reinterpret_cast<const int16*>(InputPCMData.GetData());
|
|
uint8* EncodedADPCMChannelData = EncodedADPCMData;
|
|
|
|
// Encode each channel, appending channel output as we go.
|
|
for (uint32 ChannelIndex = 0; ChannelIndex < QualityInfo.NumChannels; ++ChannelIndex)
|
|
{
|
|
const int16* ChannelPCMSamples = InputPCMSamples + ChannelIndex;
|
|
int32 SourceSampleOffset = 0;
|
|
int32 DestDataOffset = 0;
|
|
|
|
for (int32 BlockIndex = 0; BlockIndex < NumBlocksPerChannel; ++BlockIndex)
|
|
{
|
|
EncodeBlock(ChannelPCMSamples + SourceSampleOffset, SourceSampleStride, SourceNumSamples - SourceSampleOffset, BlockSize, EncodedADPCMChannelData + DestDataOffset);
|
|
|
|
SourceSampleOffset += CompressedSamplesPerBlock * SourceSampleStride;
|
|
DestDataOffset += BlockSize;
|
|
}
|
|
|
|
EncodedADPCMChannelData += DestDataOffset;
|
|
}
|
|
|
|
ADPCMFormatHeader Format;
|
|
Format.BaseFormat.nChannels = static_cast<uint16>(QualityInfo.NumChannels);
|
|
Format.BaseFormat.nSamplesPerSec = QualityInfo.SampleRate;
|
|
Format.BaseFormat.nBlockAlign = static_cast<uint16>(BlockSize);
|
|
Format.BaseFormat.wBitsPerSample = 4;
|
|
Format.BaseFormat.wFormatTag = WAVE_FORMAT_ADPCM;
|
|
Format.wSamplesPerBlock = static_cast<uint16>(CompressedSamplesPerBlock);
|
|
Format.BaseFormat.nAvgBytesPerSec = ((Format.BaseFormat.nSamplesPerSec / Format.wSamplesPerBlock) * Format.BaseFormat.nBlockAlign);
|
|
Format.wNumCoef = NUM_ADAPTATION_COEFF;
|
|
Format.BaseFormat.cbSize = sizeof(Format) - sizeof(Format.BaseFormat);
|
|
|
|
RiffDataChunk RiffDataChunks[2];
|
|
RiffDataChunks[0].ID = UE_MAKEFOURCC('f','m','t',' ');
|
|
RiffDataChunks[0].DataSize = sizeof(Format);
|
|
RiffDataChunks[0].Data = reinterpret_cast<uint8*>(&Format);
|
|
|
|
RiffDataChunks[1].ID = UE_MAKEFOURCC('d','a','t','a');
|
|
RiffDataChunks[1].DataSize = EncodedADPCMDataSize;
|
|
RiffDataChunks[1].Data = EncodedADPCMData;
|
|
|
|
GenerateWaveFile(RiffDataChunks, CompressedDataStore);
|
|
FMemory::Free(EncodedADPCMData);
|
|
}
|
|
} // end namespace ADPCM
|
|
|
|
class FAudioFormatADPCM : public IAudioFormat
|
|
{
|
|
enum
|
|
{
|
|
/** Version for ADPCM format, this becomes part of the DDC key. */
|
|
UE_AUDIO_ADPCM_VER = 0,
|
|
};
|
|
|
|
public:
|
|
virtual bool AllowParallelBuild() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual uint16 GetVersion(FName Format) const OVERRIDE
|
|
{
|
|
check(Format == NAME_ADPCM);
|
|
return UE_AUDIO_ADPCM_VER;
|
|
}
|
|
|
|
|
|
virtual void GetSupportedFormats(TArray<FName>& OutFormats) const
|
|
{
|
|
OutFormats.Add(NAME_ADPCM);
|
|
}
|
|
|
|
virtual bool Cook(FName Format, const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo, TArray<uint8>& CompressedDataStore) const
|
|
{
|
|
check(Format == NAME_ADPCM);
|
|
|
|
if (QualityInfo.Quality == 100)
|
|
{
|
|
LPCM::Encode(SrcBuffer, CompressedDataStore, QualityInfo);
|
|
}
|
|
else
|
|
{
|
|
ADPCM::Encode(SrcBuffer, CompressedDataStore, QualityInfo);
|
|
}
|
|
|
|
return CompressedDataStore.Num() > 0;
|
|
}
|
|
|
|
virtual bool CookSurround(FName Format, const TArray<TArray<uint8> >& SrcBuffers, FSoundQualityInfo& QualityInfo, TArray<uint8>& CompressedDataStore) const
|
|
{
|
|
check(Format == NAME_ADPCM);
|
|
|
|
// Unsupported for iOS devices
|
|
return false;
|
|
}
|
|
|
|
virtual int32 Recompress(FName Format, const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo, TArray<uint8>& OutBuffer) const
|
|
{
|
|
check(Format == NAME_ADPCM);
|
|
|
|
// Recompress is only necessary during editor previews
|
|
return 0;
|
|
}
|
|
|
|
virtual bool HasCompressedAudioInfoClass() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual class ICompressedAudioInfo* CreateCompressedAudioInfo() const
|
|
{
|
|
// There is no equivalent of FVorbisAudioInfo for ADPCM
|
|
return NULL;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Module for ADPCM audio compression
|
|
*/
|
|
|
|
static IAudioFormat* Singleton = NULL;
|
|
|
|
class FAudioPlatformADPCMModule : public IAudioFormatModule
|
|
{
|
|
public:
|
|
virtual ~FAudioPlatformADPCMModule()
|
|
{
|
|
delete Singleton;
|
|
Singleton = NULL;
|
|
}
|
|
virtual IAudioFormat* GetAudioFormat()
|
|
{
|
|
if (!Singleton)
|
|
{
|
|
/* TODO::JTM - Dec 10, 2012 09:55AM - Library initialization */
|
|
Singleton = new FAudioFormatADPCM();
|
|
}
|
|
return Singleton;
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_MODULE(FAudioPlatformADPCMModule, AudioFormatADPCM);
|