Global update: full working with compressed formats, pre-importing audio files and much more. Check the updated documentation.

This commit is contained in:
Georgy Treshchev
2021-03-04 17:06:21 +01:00
parent ce9fa5692a
commit 7a4f419d53
32 changed files with 2063 additions and 575 deletions
+3 -2
View File
@@ -1,6 +1,7 @@
![GitHub](https://img.shields.io/github/license/Respirant/RuntimeAudioImporter)
# RuntimeAudioImporter
This plugin will be useful for those who want to be able to import audio files of MP3, WAV and FLAC formats into the game in runtime (for example, if the game involves importing audio files from the player's device memory). It's easy to use in both C++ and Blueprints.
Open-source plugin to import audio files of MP3, WAV and FLAC formats into the game at runtime. It's easy to use in both C++ and Blueprints.
Plugin does not require any static libraries and external dependencies.
![Runtime Audio Importer Unreal Engine Plugin Logo](image/runtimeaudioimporter.png "RuntimeAudioImporter Unreal Engine Plugin Logo")
@@ -10,7 +11,7 @@ This plugin will be useful for those who want to be able to import audio files o
- Supported for major audio formats: MP3, WAV and FLAC
- Automatic detection of audio format (by extension)
- System for obtaining transcoding status for error detection
- No any external dependencies
- No any static libraries and external dependencies
- Support for all available devices (tested on Windows and Android, but there are no restrictions to work on other devices)
## Documentation
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -1,21 +1,21 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "RuntimeAudioImporter",
"Description": "Runtime Audio Importer plugin for Unreal Engine 4. Supports mp3, wav and flac formats.",
"Category": "Audio",
"CreatedBy": "Respirant",
"CreatedByURL": "https://github.com/Respirant/RuntimeAudioImporter",
"DocsURL": "https://unreal.blog/runtime-audio-importer",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036",
"SupportURL": "https://telegram.me/Respirant",
"CanContainContent": false,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": true,
"EngineVersion": "4.26.0",
"Modules": [
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "RuntimeAudioImporter",
"Description": "Runtime Audio Importer plugin for Unreal Engine 4. Supports mp3, wav and flac formats.",
"Category": "Audio",
"CreatedBy": "Georgy Treshchev",
"CreatedByURL": "https://github.com/Respirant/RuntimeAudioImporter",
"DocsURL": "https://unreal.blog/runtime-audio-importer",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036",
"SupportURL": "https://t.me/unrealblog",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": true,
"EngineVersion": "4.26.0",
"Modules": [
{
"Name": "RuntimeAudioImporter",
"Type": "Runtime",
@@ -34,6 +34,10 @@
"TVOS",
"Switch"
]
},
{
"Name": "RuntimeAudioImporterEditor",
"Type": "Editor"
}
]
}
@@ -0,0 +1,346 @@
// Georgy Treshchev 2021.
#include "AdvancedPCMCompressor.h"
#include "Interfaces/IAudioFormat.h"
#include "Serialization/MemoryWriter.h"
#include "ADPCMAudioInfo.h"
#define FORMAT_FOURCC(ch0, ch1, ch2, ch3)\
((uint32)(uint8)(ch0) | ((uint32)(uint8)(ch1) << 8) |\
((uint32)(uint8)(ch2) << 16) | ((uint32)(uint8)(ch3) << 24 ))
namespace BaseDataOperations
{
template <uint32 N>
void GenerateWaveFile(RiffDataChunk (& RiffDataChunks)[N], TArray<uint8>& CompressedDataStore)
{
uint32 RiffDataSize = sizeof(uint32);
for (uint32 Scan = 0; Scan < N; ++Scan)
{
const RiffDataChunk& Chunk = RiffDataChunks[Scan];
RiffDataSize += (sizeof(Chunk.ID) + sizeof(Chunk.DataSize) + Chunk.DataSize);
}
const uint32 OutputDataSize = RiffDataSize + sizeof(uint32) + sizeof(uint32);
CompressedDataStore.Empty(OutputDataSize);
FMemoryWriter CompressedData(CompressedDataStore);
uint32 rID = FORMAT_FOURCC('R', 'I', 'F', 'F');
CompressedData.Serialize(&rID, sizeof(rID));
CompressedData.Serialize(&RiffDataSize, sizeof(RiffDataSize));
rID = FORMAT_FOURCC('W', 'A', 'V', 'E');
CompressedData.Serialize(&rID, sizeof(rID));
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>
T SignExtend(const T ValueToExtend)
{
struct
{
T ExtendedValue:B;
} SignExtender;
return SignExtender.ExtendedValue = ValueToExtend;
}
template <typename T>
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>
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>
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;
}
} // end namespace
namespace LPCM
{
void GenerateCompressedData(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 = FORMAT_FOURCC('f', 'm', 't', ' ');
RiffDataChunks[0].DataSize = sizeof(Format);
RiffDataChunks[0].Data = reinterpret_cast<uint8*>(&Format);
RiffDataChunks[1].ID = FORMAT_FOURCC('d', 'a', 't', 'a');
RiffDataChunks[1].DataSize = InputPCMData.Num();
RiffDataChunks[1].Data = const_cast<uint8*>(InputPCMData.GetData());
BaseDataOperations::GenerateWaveFile(RiffDataChunks, CompressedDataStore);
}
} // end namespace LPCM
namespace ADPCM
{
template <typename T>
static void GetAdaptationTable(T (& OutAdaptationTable)[NUM_ADAPTATION_TABLE])
{
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));
}
struct FAdaptationContext
{
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);
}
};
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);
PredictedSample += (Context.AdaptationDelta * ErrorDelta);
PredictedSample = FMath::Clamp(PredictedSample, -32768, 32767);
int8 SmallDelta = static_cast<int8>(ErrorDelta);
const uint8 EncodedNibble = reinterpret_cast<uint8&>(SmallDelta) & 0x0F;
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;
const uint8 CoefficientIndex = 0;
Context.AdaptationDelta = Context.AdaptationTable[0];
Context.Sample2 = BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride);
Context.Sample1 = BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride);
Context.Coefficient1 = Context.AdaptationCoefficient1[CoefficientIndex];
Context.Coefficient2 = Context.AdaptationCoefficient2[CoefficientIndex];
BaseDataOperations::WriteToByteStream<uint8>(CoefficientIndex, EncodedADPCMData, WriteIndex);
BaseDataOperations::WriteToByteStream<int16>(Context.AdaptationDelta, EncodedADPCMData, WriteIndex);
BaseDataOperations::WriteToByteStream<int16>(Context.Sample1, EncodedADPCMData, WriteIndex);
BaseDataOperations::WriteToByteStream<int16>(Context.Sample2, EncodedADPCMData, WriteIndex);
while (WriteIndex < BlockSize)
{
EncodedADPCMData[WriteIndex] = EncodeNibble(
Context, BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride)) << 4;
EncodedADPCMData[WriteIndex++] |= EncodeNibble(
Context, BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride));
}
return WriteIndex;
}
void GenerateCompressedData(const TArray<uint8>& InputPCMData, TArray<uint8>& CompressedDataStore,
const FSoundQualityInfo& QualityInfo)
{
const int32 SourceSampleStride = QualityInfo.NumChannels;
const int32 SourceNumSamples = QualityInfo.SampleDataSize / 2;
const int32 SourceNumSamplesPerChannel = SourceNumSamples / QualityInfo.NumChannels;
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;
const int32 NumBlocksPerChannel = (SourceNumSamplesPerChannel + CompressedSamplesPerBlock - 1) /
CompressedSamplesPerBlock;
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;
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.SamplesPerChannel = SourceNumSamplesPerChannel;
Format.BaseFormat.cbSize = sizeof(Format) - sizeof(Format.BaseFormat);
RiffDataChunk RiffDataChunks[2];
RiffDataChunks[0].ID = FORMAT_FOURCC('f', 'm', 't', ' ');
RiffDataChunks[0].DataSize = sizeof(Format);
RiffDataChunks[0].Data = reinterpret_cast<uint8*>(&Format);
RiffDataChunks[1].ID = FORMAT_FOURCC('d', 'a', 't', 'a');
RiffDataChunks[1].DataSize = EncodedADPCMDataSize;
RiffDataChunks[1].Data = EncodedADPCMData;
BaseDataOperations::GenerateWaveFile(RiffDataChunks, CompressedDataStore);
FMemory::Free(EncodedADPCMData);
}
} // end namespace ADPCM
bool FAdvancedPCMCompressor::GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& OutBuffer)
{
if (QualityInfo.Quality == 100)
{
LPCM::GenerateCompressedData(SrcBuffer, OutBuffer, QualityInfo);
}
else
{
ADPCM::GenerateCompressedData(SrcBuffer, OutBuffer, QualityInfo);
}
return OutBuffer.Num() > 0;
}
void FAdvancedPCMCompressor::AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize)
{
TArray<uint8>& NewBuffer = *new(OutBuffers) TArray<uint8>;
NewBuffer.Empty(ChunkReserveSize);
}
void FAdvancedPCMCompressor::AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData,
int32 ChunkDataSize)
{
TArray<uint8>& TargetBuffer = OutBuffers[OutBuffers.Num() - 1];
TargetBuffer.Append(ChunkData, ChunkDataSize);
}
@@ -0,0 +1,202 @@
// Georgy Treshchev 2021.
#include "OpusCompressor.h"
#include "Interfaces/IAudioFormat.h"
#include "Serialization/MemoryWriter.h"
#include "OpusAudioInfo.h"
#define OUTSIDE_SPEEX
THIRD_PARTY_INCLUDES_START
#include "opus_multistream.h"
#include "speex_resampler.h"
THIRD_PARTY_INCLUDES_END
#define SAMPLE_SIZE ( ( uint32 )sizeof( short ) )
bool FOpusCompressor::GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& CompressedDataStore)
{
const uint16 OpusSampleRate = GetBestOutputSampleRate(QualityInfo.SampleRate);
const int32 OpusFrameSizeMs = 60;
const int32 OpusFrameSizeSamples = (OpusSampleRate * OpusFrameSizeMs) / 1000;
const uint32 SampleStride = SAMPLE_SIZE * QualityInfo.NumChannels;
const int32 BytesPerFrame = OpusFrameSizeSamples * SampleStride;
TArray<uint8> SrcBufferCopy;
if (QualityInfo.SampleRate != OpusSampleRate)
{
if (!ResamplePCM(QualityInfo.NumChannels, SrcBuffer, QualityInfo.SampleRate, SrcBufferCopy, OpusSampleRate))
{
return false;
}
}
else
{
SrcBufferCopy = SrcBuffer;
}
const int32 EncSize = opus_encoder_get_size(QualityInfo.NumChannels);
OpusEncoder* Encoder = static_cast<OpusEncoder*>(FMemory::Malloc(EncSize));
const int32 EncError = opus_encoder_init(Encoder, OpusSampleRate, QualityInfo.NumChannels, OPUS_APPLICATION_AUDIO);
if (EncError != OPUS_OK)
{
FMemory::Free(Encoder);
return false;
}
const int32 BitRate = GetBitRateFromQuality(QualityInfo);
opus_encoder_ctl(Encoder, OPUS_SET_BITRATE(BitRate));
CompressedDataStore.Empty();
FMemoryWriter CompressedData(CompressedDataStore);
int32 SrcBufferOffset = 0;
int32 FramesToEncode = SrcBufferCopy.Num() / BytesPerFrame;
const uint32 AccurateSampleCount = SrcBufferCopy.Num() / SampleStride;
if (SrcBufferCopy.Num() % BytesPerFrame != 0)
{
int32 FrameDiff = BytesPerFrame - (SrcBufferCopy.Num() % BytesPerFrame);
SrcBufferCopy.AddZeroed(FrameDiff);
FramesToEncode++;
}
check(QualityInfo.NumChannels <= MAX_uint8);
check(FramesToEncode <= MAX_uint16);
SerializeHeaderData(CompressedData, OpusSampleRate, AccurateSampleCount, QualityInfo.NumChannels, FramesToEncode);
TArray<uint8> TempCompressedData;
TempCompressedData.AddUninitialized(BytesPerFrame);
while (SrcBufferOffset < SrcBufferCopy.Num())
{
const int32 CompressedDataLength = opus_encode(
Encoder, reinterpret_cast<const opus_int16*>(SrcBufferCopy.GetData() + SrcBufferOffset),
OpusFrameSizeSamples, TempCompressedData.GetData(), TempCompressedData.Num());
if (CompressedDataLength < 0)
{
const char* ErrorStr = opus_strerror(CompressedDataLength);
UE_LOG(LogAudio, Warning, TEXT("Failed to encode: [%d] %s"), CompressedDataLength, ANSI_TO_TCHAR(ErrorStr));
FMemory::Free(Encoder);
CompressedDataStore.Empty();
return false;
}
else
{
check(CompressedDataLength < MAX_uint16);
SerialiseFrameData(CompressedData, TempCompressedData.GetData(), CompressedDataLength);
SrcBufferOffset += BytesPerFrame;
}
}
FMemory::Free(Encoder);
return CompressedDataStore.Num() > 0;
}
uint16 FOpusCompressor::GetBestOutputSampleRate(int32 SampleRate)
{
static const uint16 ValidSampleRates[] =
{
0,
8000,
12000,
16000,
24000,
48000,
};
for (int32 Index = UE_ARRAY_COUNT(ValidSampleRates) - 2; Index >= 0; Index--)
{
if (SampleRate > ValidSampleRates[Index])
{
return ValidSampleRates[Index + 1];
}
}
check(0);
return 0;
}
bool FOpusCompressor::ResamplePCM(uint32 NumChannels, const TArray<uint8>& InBuffer, uint32 InSampleRate,
TArray<uint8>& OutBuffer, uint32 OutSampleRate)
{
int32 err = 0;
SpeexResamplerState* resampler = speex_resampler_init(NumChannels, InSampleRate, OutSampleRate,
SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
if (err != RESAMPLER_ERR_SUCCESS)
{
speex_resampler_destroy(resampler);
return false;
}
const uint32 SampleStride = SAMPLE_SIZE * NumChannels;
const float Duration = (float)InBuffer.Num() / (InSampleRate * SampleStride);
const int32 SafeCopySize = (Duration + 1) * OutSampleRate * SampleStride;
OutBuffer.Empty(SafeCopySize);
OutBuffer.AddUninitialized(SafeCopySize);
uint32 InSamples = InBuffer.Num() / SampleStride;
uint32 OutSamples = OutBuffer.Num() / SampleStride;
if (NumChannels == 1)
{
err = speex_resampler_process_int(resampler, 0, (const short*)(InBuffer.GetData()), &InSamples,
(short*)(OutBuffer.GetData()), &OutSamples);
}
else
{
err = speex_resampler_process_interleaved_int(resampler, (const short*)(InBuffer.GetData()), &InSamples,
(short*)(OutBuffer.GetData()), &OutSamples);
}
speex_resampler_destroy(resampler);
if (err != RESAMPLER_ERR_SUCCESS)
{
return false;
}
const int32 WrittenBytes = (int32)(OutSamples * SampleStride);
if (WrittenBytes < OutBuffer.Num())
{
OutBuffer.SetNum(WrittenBytes, true);
}
return true;
}
int32 FOpusCompressor::GetBitRateFromQuality(FSoundQualityInfo& QualityInfo)
{
const int32 OriginalBitRate = QualityInfo.SampleRate * QualityInfo.NumChannels * SAMPLE_SIZE * 8;
return static_cast<float>(OriginalBitRate) * FMath::GetMappedRangeValueClamped(
FVector2D(1, 100), FVector2D(0.04, 0.25), QualityInfo.Quality);
}
void FOpusCompressor::SerializeHeaderData(FMemoryWriter& CompressedData, uint16 SampleRate, uint32 TrueSampleCount,
uint8 NumChannels, uint16 NumFrames)
{
const char* OpusIdentifier = OPUS_ID_STRING;
CompressedData.Serialize((void*)OpusIdentifier, FCStringAnsi::Strlen(OpusIdentifier) + 1);
CompressedData.Serialize(&SampleRate, sizeof(uint16));
CompressedData.Serialize(&TrueSampleCount, sizeof(uint32));
CompressedData.Serialize(&NumChannels, sizeof(uint8));
CompressedData.Serialize(&NumFrames, sizeof(uint16));
}
void FOpusCompressor::SerialiseFrameData(FMemoryWriter& CompressedData, uint8* FrameData, uint16 FrameSize)
{
CompressedData.Serialize(&FrameSize, sizeof(uint16));
CompressedData.Serialize(FrameData, FrameSize);
}
int32 FOpusCompressor::AddDataChunk(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkSize)
{
TArray<uint8>& NewBuffer = *new(OutBuffers) TArray<uint8>;
NewBuffer.Empty(ChunkSize);
NewBuffer.AddUninitialized(ChunkSize);
FMemory::Memcpy(NewBuffer.GetData(), ChunkData, ChunkSize);
return ChunkSize;
}
@@ -0,0 +1,6 @@
// Georgy Treshchev 2021.
#include "PreimportedSoundAsset.h"
@@ -1,4 +1,4 @@
// Respirant 2020.
// Georgy Treshchev 2021.
#include "RuntimeAudioImporter.h"
@@ -6,15 +6,10 @@
void FRuntimeAudioImporterModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FRuntimeAudioImporterModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
}
#undef LOCTEXT_NAMESPACE
@@ -1,11 +1,16 @@
// Respirant 2020.
// Georgy Treshchev 2021.
#include "RuntimeAudioImporterLibrary.h"
URuntimeAudioImporterLibrary::URuntimeAudioImporterLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
#include "AudioDevice.h"
#include "AudioCompressionSettingsUtils.h"
#include "Interfaces/IAudioFormat.h"
// Compressors for platform-specific audio formats
#include "VorbisCompressor.h"
#include "OpusCompressor.h"
#include "AdvancedPCMCompressor.h"
URuntimeAudioImporterLibrary* URuntimeAudioImporterLibrary::CreateRuntimeAudioImporter()
{
@@ -14,14 +19,48 @@ URuntimeAudioImporterLibrary* URuntimeAudioImporterLibrary::CreateRuntimeAudioIm
return Importer;
}
URuntimeAudioImporterLibrary* URuntimeAudioImporterLibrary::ImportAudioFromFile(
const FString& FilePath, TEnumAsByte<AudioFormat> Format, bool DefineFormatAutomatically)
void URuntimeAudioImporterLibrary::ImportAudioFromFile(
const FString& FilePath, TEnumAsByte<EAudioFormat> Format, const FBuffersDetailsStruct& BuffersDetails)
{
if (!FPaths::FileExists(FilePath))
{
// Trying to get a file inside packaged build
IPlatformFile& FileManager = FPlatformFileManager::Get().GetPlatformFile();
OnResult_Internal(nullptr, ETranscodingStatus::AudioDoesNotExist);
return;
}
if (Format == EAudioFormat::Auto)
{
Format = GetAudioFormat(FilePath);
}
TArray<uint8> AudioBuffer;
if (!FFileHelper::LoadFileToArray(AudioBuffer, *FilePath))
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::LoadFileToArrayError);
return;
}
ImportAudioFromBuffer(AudioBuffer, Format, BuffersDetails);
}
void URuntimeAudioImporterLibrary::ImportAudioFromPreimportedSound(UPreimportedSoundAsset* PreimportedSoundAssetRef,
const FBuffersDetailsStruct& BuffersDetails)
{
ImportAudioFromBuffer(PreimportedSoundAssetRef->AudioDataArray, EAudioFormat::Mp3, BuffersDetails);
}
void URuntimeAudioImporterLibrary::ImportAudioFromBuffer(const TArray<uint8>& AudioDataArray,
const TEnumAsByte<EAudioFormat>& Format,
const FBuffersDetailsStruct& BuffersDetails)
{
AsyncTask(ENamedThreads::AnyThread, [=]()
{
GetUSoundWaveFromAudioFile_Internal(FilePath, Format, DefineFormatAutomatically);
ImportAudioFromBuffer_Internal(AudioDataArray, Format, BuffersDetails);
});
return this;
}
bool URuntimeAudioImporterLibrary::DestroySoundWave(USoundWave* ReadySoundWave)
@@ -52,431 +91,540 @@ bool URuntimeAudioImporterLibrary::DestroySoundWave(USoundWave* ReadySoundWave)
}
}
/**
* Including dr_wav, dr_mp3 and dr_flac libraries
*/
#include "ThirdParty/dr_wav.h"
#include "ThirdParty/dr_mp3.h"
#include "ThirdParty/dr_flac.h"
/**
* Replacing standard CPP memory functions (malloc, realloc, free) with memory management functions that are maintained by Epic as the engine evolves.
*/
void* unreal_malloc(size_t sz, void* pUserData)
void URuntimeAudioImporterLibrary::ImportAudioFromBuffer_Internal(
const TArray<uint8>& AudioDataArray, const TEnumAsByte<EAudioFormat>& Format,
const FBuffersDetailsStruct& BuffersDetails)
{
return FMemory::Malloc(sz);
}
BuffersDetailsInfo = BuffersDetails;
void* unreal_realloc(void* p, size_t sz, void* pUserData)
{
return FMemory::Realloc(p, sz);
}
void unreal_free(void* p, void* pUserData)
{
FMemory::Free(p);
}
bool URuntimeAudioImporterLibrary::ExportSoundWaveToFile(USoundWave* SoundWaveToExport, FString PathToExport)
{
uint8* PCMData = SoundWaveToExport->RawPCMData;
size_t PCMDataSize = SoundWaveToExport->RawPCMDataSize;
drwav_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = NULL;
allocationCallbacksDecoding.onMalloc = unreal_malloc;
allocationCallbacksDecoding.onRealloc = unreal_realloc;
allocationCallbacksDecoding.onFree = unreal_free;
drwav_uint64 framesToWrite = PCMDataSize / sizeof(DR_WAVE_FORMAT_PCM) / SoundWaveToExport->NumChannels;
uint16 ChannelCount = SoundWaveToExport->NumChannels;
uint32 SampleRate = FGenericPlatformMath::RoundToInt(
(framesToWrite / (float)SoundWaveToExport->GetDuration() * ChannelCount));
drwav wavEncode;
drwav_data_format format;
format.container = drwav_container_riff;
format.format = DR_WAVE_FORMAT_PCM;
format.channels = ChannelCount;
format.sampleRate = SampleRate;
format.bitsPerSample = 16;
#if PLATFORM_WINDOWS
if (!drwav_init_file_write_w(&wavEncode, (wchar_t*)PathToExport.GetCharArray().GetData(), &format,
&allocationCallbacksDecoding))
#else
if (!drwav_init_file_write(&wavEncode, TCHAR_TO_UTF8(*PathToExport), &format,
&allocationCallbacksDecoding))
#endif
if (Format == EAudioFormat::Auto || Format == EAudioFormat::Invalid)
{
return false;
}
drwav_write_pcm_frames(&wavEncode, framesToWrite, PCMData);
drwav_uninit(&wavEncode);
return true;
}
URuntimeAudioImporterLibrary* URuntimeAudioImporterLibrary::GetUSoundWaveFromAudioFile_Internal(
const FString& FilePath, TEnumAsByte<AudioFormat> Format, bool DefineFormatAutomatically)
{
TEnumAsByte<TranscodingStatus> Status;
if (FPaths::FileExists(FilePath) != true)
{
Status = TranscodingStatus::AudioDoesNotExist;
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, Status);
OnResult_Internal(nullptr, ETranscodingStatus::InvalidAudioFormat);
return;
}
else
// Transcoding importer Audio Data to PCM Data
if (!TranscodeAudioDataArrayToPCMData(AudioDataArray.GetData(), AudioDataArray.Num() - 2, Format))
{
int16_t* pSampleData;
uint64 framesToWrite;
uint32 channels;
uint32 sampleRate;
if (DefineFormatAutomatically)
{
Format = GetAudioFormat(FilePath);
}
if (Format == AudioFormat::INVALID)
{
Status = TranscodingStatus::InvalidAudioFormat;
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, Status);
}
else
{
if (TranscodeAudioFileToPCMData(FilePath, Format, Status, framesToWrite, pSampleData, channels, sampleRate)
== false)
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, Status);
}
else
{
void* WaveData;
size_t WaveDataSize;
if (TranscodePCMToWAVData(*&WaveData, *&WaveDataSize, framesToWrite, *&pSampleData, channels,
sampleRate) == false)
{
FMemory::Free(pSampleData);
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, Status);
}
else
{
USoundWave* ReadyUSoundWave = GetSoundWaveObject((const uint8*)WaveData, WaveDataSize, Status);
// A ready USoundWave static object
/* Free temporary transcoding data */
FMemory::Free(WaveData);
FMemory::Free(pSampleData);
/* Free temporary transcoding data */
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(100);
// Callback Dispatcher OnResult
OnResult_Internal(ReadyUSoundWave, Status);
}
}
}
return;
}
return this;
// A ready USoundWave static object
USoundWave* ReadySoundWave = DefineSoundWave();
if (ReadySoundWave == nullptr)
{
return;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(100);
// Callback Dispatcher OnResult, with the created SoundWave object
OnResult_Internal(ReadySoundWave, ETranscodingStatus::SuccessImporting);
}
class USoundWave* URuntimeAudioImporterLibrary::GetSoundWaveObject(const uint8* WaveData, int32 WaveDataSize,
TEnumAsByte<TranscodingStatus>& Status)
USoundWave* URuntimeAudioImporterLibrary::DefineSoundWave()
{
USoundWave* sw = NewObject<USoundWave>(USoundWave::StaticClass());
if (!sw)
USoundWave* SoundWaveRef = NewObject<USoundWave>(USoundWave::StaticClass());
if (!SoundWaveRef)
{
Status = TranscodingStatus::USoundWaveDeclarationError;
OnResult_Internal(nullptr, ETranscodingStatus::SoundWaveDeclarationError);
return nullptr;
}
FWaveModInfo WaveInfo;
FString ErrorReason;
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(65);
// Callback Dispatcher OnProgress
OnProgress_Internal(40);
if (WaveInfo.ReadWaveInfo(WaveData, WaveDataSize, &ErrorReason))
// Fill SoundWave basic information (e.g. duration, number of channels, etc)
FillSoundWaveBasicInfo(SoundWaveRef);
// Callback Dispatcher OnProgress
OnProgress_Internal(45);
// Whether to fill the compressed buffer or not. If not filled, it is necessary to fill Raw (Wave) data so that the engine will automatically generate compressed data.
if (!BuffersDetailsInfo.CompressionFormat == ECompressionFormat::RawData)
{
// Callback Dispatcher OnProgress (not completely accurate implementation)
// Callback Dispatcher OnProgress
OnProgress_Internal(60);
// Transcode PCM to Raw (Wave) Data
if (!TranscodePCMToWAVData())
{
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
return nullptr;
}
// free unneeded PCM Data
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
// Callback Dispatcher OnProgress
OnProgress_Internal(75);
if (*WaveInfo.pBitsPerSample != 16)
{
Status = TranscodingStatus::UnsupportedBitDepth;
return nullptr;
}
sw->RawData.Lock(LOCK_READ_WRITE);
FMemory::Memcpy(sw->RawData.Realloc(WaveDataSize), WaveData, WaveDataSize);
sw->RawData.Unlock();
// Fill Raw (Wave) Data
FillRawWaveData(SoundWaveRef);
OnProgress_Internal(85);
// RawPCMDataSize = WaveDataSize - 44 bytes (the header of the WAV data) = SampleDataSize
sw->RawPCMDataSize = WaveInfo.SampleDataSize;
sw->RawPCMData = (uint8*)FMemory::Malloc(sw->RawPCMDataSize);
FMemory::Memmove(sw->RawPCMData, WaveData, sw->RawPCMDataSize);
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(90);
int32 DurationDiv = *WaveInfo.pChannels * *WaveInfo.pBitsPerSample * *WaveInfo.pSamplesPerSec;
if (DurationDiv)
{
sw->Duration = *WaveInfo.pWaveDataSize * 8.0f / DurationDiv;
}
else
{
sw->Duration = 0.0f;
Status = TranscodingStatus::ZeroDurationError;
return nullptr;
}
sw->SetSampleRate(*WaveInfo.pSamplesPerSec);
sw->NumChannels = *WaveInfo.pChannels;
sw->RawPCMDataSize = WaveInfo.SampleDataSize;
sw->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
// Callback Dispatcher OnProgress (not completely accurate implementation)
// Callback Dispatcher OnProgress
OnProgress_Internal(95);
return SoundWaveRef;
}
else
/* We don't need to fill RawPCMData buffer since it is not used at runtime during audio playback. But in case you want to fill this buffer - uncomment the code below (you should also remove some RawPCMData cleanups in code before it is executed)
sw->RawPCMData = static_cast<uint8*>(FMemory::Malloc(sw->RawPCMDataSize));
FMemory::Memmove(sw->RawPCMData, TranscodingFillInfo.PCMInfo.RawPCMData, sw->RawPCMDataSize);
*/
// Set a new sound compression quality
SoundWaveRef->CompressionQuality = BuffersDetailsInfo.SoundCompressionQuality;
// This variable won't be used anywhere, but it is recommended to set a decompression type to realtime since it will be decompressed at runtime
SoundWaveRef->DecompressionType = EDecompressionType::DTYPE_RealTime;
// Callback Dispatcher OnProgress
OnProgress_Internal(60);
// Transcode Raw PCM data to Compressed data
if (!TranscodePCMToCompressedData(SoundWaveRef))
{
if (ErrorReason.Equals(TEXT("Invalid WAVE file."), ESearchCase::CaseSensitive))
{
Status = TranscodingStatus::InvalidWAVEData;
}
else if (ErrorReason.Equals(
TEXT("Unsupported WAVE file format: actual bit rate does not match the container size."),
ESearchCase::CaseSensitive))
{
Status = TranscodingStatus::InvalidBitrate;
}
else if (ErrorReason.Equals(
TEXT("Unsupported WAVE file format: subformat identifier not recognized."), ESearchCase::CaseSensitive))
{
Status = TranscodingStatus::InvalidSubformat;
}
else
{
Status = TranscodingStatus::UnrecognizedReadWaveError;
}
return nullptr;
}
if (!sw)
{
Status = TranscodingStatus::InvalidUSoundWave;
return nullptr;
}
Status = TranscodingStatus::SuccessTranscoding;
return sw;
// Callback Dispatcher OnProgress
OnProgress_Internal(75);
SoundWaveRef->SetPrecacheState(ESoundWavePrecacheState::NotStarted);
// Fill the compressed data
FillCompressedData(SoundWaveRef);
// Callback Dispatcher OnProgress
OnProgress_Internal(95);
return SoundWaveRef;
}
bool URuntimeAudioImporterLibrary::TranscodeAudioFileToPCMData(FString FilePath, TEnumAsByte<AudioFormat> Format,
TEnumAsByte<TranscodingStatus>& Status,
uint64& framesToWrite, int16_t*& pSampleData,
uint32& channels, uint32& sampleRate)
void URuntimeAudioImporterLibrary::FillSoundWaveBasicInfo(USoundWave* SoundWaveRef) const
{
OnProgress_Internal(5);
switch (Format)
SoundWaveRef->RawPCMDataSize = TranscodingFillInfo.PCMInfo.RawPCMDataSize;
SoundWaveRef->Duration = TranscodingFillInfo.SoundWaveBasicInfo.Duration;
SoundWaveRef->SetSampleRate(TranscodingFillInfo.SoundWaveBasicInfo.SampleRate);
SoundWaveRef->NumChannels = TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum;
SoundWaveRef->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
if (SoundWaveRef->NumChannels == 4)
{
case AudioFormat::MP3:
SoundWaveRef->bIsAmbisonics = 1;
}
}
void URuntimeAudioImporterLibrary::FillRawWaveData(USoundWave* SoundWaveRef) const
{
SoundWaveRef->RawData.Lock(LOCK_READ_WRITE);
FMemory::Memmove(SoundWaveRef->RawData.Realloc(TranscodingFillInfo.WAVInfo.WaveDataSize),
TranscodingFillInfo.WAVInfo.WaveData, TranscodingFillInfo.WAVInfo.WaveDataSize);
SoundWaveRef->RawData.Unlock();
}
void URuntimeAudioImporterLibrary::FillCompressedData(USoundWave* SoundWaveRef)
{
// Get the name of the runtime format that will be used during audio playback
const FName CurrentAudioFormat = GEngine->GetAudioDeviceManager()->GetActiveAudioDevice()->
GetRuntimeFormat(SoundWaveRef);
SoundWaveRef->SetPrecacheState(ESoundWavePrecacheState::InProgress);
// Fill the compressed data
FByteBulkData* CompressedBulkData = &SoundWaveRef->CompressedFormatData.GetFormat(
GetPlatformSpecificFormat(CurrentAudioFormat));
CompressedBulkData->Lock(LOCK_READ_WRITE);
FMemory::Memmove(CompressedBulkData->Realloc(TranscodingFillInfo.CompressedInfo.CompressedFormatData.Num()),
TranscodingFillInfo.CompressedInfo.CompressedFormatData.GetData(),
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Num());
CompressedBulkData->Unlock();
SoundWaveRef->SetPrecacheState(ESoundWavePrecacheState::Done);
}
bool URuntimeAudioImporterLibrary::TranscodePCMToCompressedData(USoundWave* SoundWaveToGetData)
{
// Raw PCM data Array
TArray<uint8> RawPCMArray = TArray<uint8>(TranscodingFillInfo.PCMInfo.RawPCMData,
TranscodingFillInfo.PCMInfo.RawPCMDataSize);
// Sound quality information for compression purposes
FSoundQualityInfo SoundQualityInfo = GenerateSoundQualityInfo(SoundWaveToGetData);
switch (BuffersDetailsInfo.CompressionFormat)
{
case OggVorbis:
{
drmp3_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = NULL;
allocationCallbacksDecoding.onMalloc = unreal_malloc;
allocationCallbacksDecoding.onRealloc = unreal_realloc;
allocationCallbacksDecoding.onFree = unreal_free;
drmp3 mp3;
#if PLATFORM_WINDOWS
if (!drmp3_init_file_w(&mp3, (wchar_t*)FilePath.GetCharArray().GetData(), &allocationCallbacksDecoding))
#else
if (!drmp3_init_file(&mp3, TCHAR_TO_UTF8(*FilePath), &allocationCallbacksDecoding))
#endif
if (!FVorbisCompressor::GenerateCompressedData(RawPCMArray, SoundQualityInfo,
TranscodingFillInfo.CompressedInfo.CompressedFormatData))
{
Status = TranscodingStatus::FailedToOpenStream;
// Clearing all data in case of error for performance reasons
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::GenerateCompressedDataError);
return false;
}
else
{
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(10);
pSampleData = (int16_t*)FMemory::Malloc(
(size_t)drmp3_get_pcm_frame_count(&mp3) * mp3.channels * sizeof(int16_t));
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(15);
framesToWrite = drmp3_read_pcm_frames_s16(&mp3, drmp3_get_pcm_frame_count(&mp3), pSampleData);
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(35);
channels = mp3.channels;
sampleRate = mp3.sampleRate;
drmp3_uninit(&mp3);
return true;
}
break;
}
case AudioFormat::WAV:
case OggOpus:
{
drwav_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = NULL;
allocationCallbacksDecoding.onMalloc = unreal_malloc;
allocationCallbacksDecoding.onRealloc = unreal_realloc;
allocationCallbacksDecoding.onFree = unreal_free;
drwav wav;
#if PLATFORM_WINDOWS
if (!drwav_init_file_w(&wav, (wchar_t*)FilePath.GetCharArray().GetData(), &allocationCallbacksDecoding))
#else
if (!drwav_init_file(&wav, TCHAR_TO_UTF8(*FilePath), &allocationCallbacksDecoding))
#endif
if (!FOpusCompressor::GenerateCompressedData(RawPCMArray, SoundQualityInfo,
TranscodingFillInfo.CompressedInfo.CompressedFormatData))
{
Status = TranscodingStatus::FailedToOpenStream;
// Clearing all data in case of error for performance reasons
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::GenerateCompressedDataError);
return false;
}
else
{
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(10);
pSampleData = (int16_t*)
FMemory::Malloc((size_t)wav.totalPCMFrameCount * wav.channels * sizeof(int16_t));
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(15);
framesToWrite = drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, pSampleData);
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(35);
channels = wav.channels;
sampleRate = wav.sampleRate;
drwav_uninit(&wav);
return true;
}
break;
}
case AudioFormat::FLAC:
case ADPCMLPCM:
{
drflac_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = NULL;
allocationCallbacksDecoding.onMalloc = unreal_malloc;
allocationCallbacksDecoding.onRealloc = unreal_realloc;
allocationCallbacksDecoding.onFree = unreal_free;
#if PLATFORM_WINDOWS
drflac* pFlac = drflac_open_file_w((wchar_t*)FilePath.GetCharArray().GetData(),
&allocationCallbacksDecoding);
#else
drflac* pFlac = drflac_open_file(TCHAR_TO_UTF8(*FilePath),
&allocationCallbacksDecoding);
#endif
if (pFlac == NULL)
if (!FAdvancedPCMCompressor::GenerateCompressedData(RawPCMArray, SoundQualityInfo,
TranscodingFillInfo.CompressedInfo.CompressedFormatData)
)
{
Status = TranscodingStatus::FailedToOpenStream;
// Clearing all data in case of error for performance reasons
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::GenerateCompressedDataError);
return false;
}
else
{
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(10);
pSampleData = (int16_t*)FMemory::Malloc(
(size_t)pFlac->totalPCMFrameCount * pFlac->channels * sizeof(int16_t));
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(15);
framesToWrite = drflac_read_pcm_frames_s16(pFlac, pFlac->totalPCMFrameCount, pSampleData);
// Callback Dispatcher OnProgress (not completely accurate implementation)
OnProgress_Internal(35);
channels = pFlac->channels;
sampleRate = pFlac->sampleRate;
drflac_close(pFlac);
return true;
}
break;
}
default:
{
Status = TranscodingStatus::InvalidAudioFormat;
// Clearing all data in case of error for performance reasons
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::InvalidCompressedFormat);
return false;
}
}
// When the compressed data is generated, RawPCMData is no longer needed.
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
// Callback Dispatcher OnProgress
OnProgress_Internal(85);
return true;
}
/**
* Including dr_wav, dr_mp3 and dr_flac libraries
*/
#include "ThirdParty/dr_wav.h"
#include "ThirdParty/dr_mp3.h"
#include "ThirdParty/dr_flac.h"
/**
* Replacing standard CPP memory functions (malloc, realloc, free) with memory management functions that are maintained by Epic as the engine evolves.
*/
void* Unreal_Malloc(size_t sz, void* pUserData)
{
return FMemory::Malloc(sz);
}
void* Unreal_Realloc(void* p, size_t sz, void* pUserData)
{
return FMemory::Realloc(p, sz);
}
void Unreal_Free(void* p, void* pUserData)
{
FMemory::Free(p);
}
bool URuntimeAudioImporterLibrary::TranscodeAudioDataArrayToPCMData(const uint8* AudioData, uint32 AudioDataSize,
TEnumAsByte<EAudioFormat> Format)
{
OnProgress_Internal(5);
switch (Format)
{
case EAudioFormat::Mp3:
{
drmp3_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = nullptr;
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
allocationCallbacksDecoding.onFree = Unreal_Free;
drmp3 mp3;
if (!drmp3_init_memory(&mp3, AudioData, AudioDataSize, &allocationCallbacksDecoding))
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::FailedToReadAudioDataArray);
return false;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(10);
TranscodingFillInfo.PCMInfo.RawPCMData = static_cast<uint8*>(FMemory::Malloc(
static_cast<size_t>(drmp3_get_pcm_frame_count(&mp3)) * mp3.channels * sizeof(int16_t)));
// Callback Dispatcher OnProgress
OnProgress_Internal(15);
TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames = drmp3_read_pcm_frames_s16(
&mp3, drmp3_get_pcm_frame_count(&mp3),
reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
// Callback Dispatcher OnProgress
OnProgress_Internal(35);
TranscodingFillInfo.PCMInfo.RawPCMDataSize = static_cast<uint32>(TranscodingFillInfo.PCMInfo.
RawPCMNumOfFrames * mp3.channels * sizeof(int16_t));
TranscodingFillInfo.SoundWaveBasicInfo.Duration = static_cast<float>(drmp3_get_pcm_frame_count(&mp3)) / mp3.
sampleRate;
TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum = mp3.channels;
TranscodingFillInfo.SoundWaveBasicInfo.SampleRate = mp3.sampleRate;
drmp3_uninit(&mp3);
return true;
}
case EAudioFormat::Wav:
{
drwav_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = nullptr;
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
allocationCallbacksDecoding.onFree = Unreal_Free;
drwav wav;
if (!drwav_init_memory(&wav, AudioData, AudioDataSize, &allocationCallbacksDecoding))
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::FailedToReadAudioDataArray);
return false;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(10);
TranscodingFillInfo.PCMInfo.RawPCMData = static_cast<uint8*>(FMemory::Malloc(
static_cast<size_t>(wav.totalPCMFrameCount) * wav.channels * sizeof(int16_t)));
// Callback Dispatcher OnProgress
OnProgress_Internal(15);
TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames = drwav_read_pcm_frames_s16(
&wav, wav.totalPCMFrameCount, reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
// Callback Dispatcher OnProgress
OnProgress_Internal(35);
TranscodingFillInfo.PCMInfo.RawPCMDataSize = static_cast<uint32>(TranscodingFillInfo.PCMInfo.
RawPCMNumOfFrames * wav.channels * sizeof(int16_t));
TranscodingFillInfo.SoundWaveBasicInfo.Duration = static_cast<float>(wav.totalPCMFrameCount) / wav.
sampleRate;
TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum = wav.channels;
TranscodingFillInfo.SoundWaveBasicInfo.SampleRate = wav.sampleRate;
drwav_uninit(&wav);
return true;
}
case EAudioFormat::Flac:
{
drflac_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = nullptr;
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
allocationCallbacksDecoding.onFree = Unreal_Free;
drflac* pFlac = drflac_open_memory(AudioData, AudioDataSize, &allocationCallbacksDecoding);
if (pFlac == nullptr)
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::FailedToReadAudioDataArray);
return false;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(10);
TranscodingFillInfo.PCMInfo.RawPCMData = static_cast<uint8*>(FMemory::Malloc(
static_cast<size_t>(pFlac->totalPCMFrameCount) * pFlac->channels * sizeof(int16_t)));
// Callback Dispatcher OnProgress
OnProgress_Internal(15);
TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames = drflac_read_pcm_frames_s16(
pFlac, pFlac->totalPCMFrameCount, reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
// Callback Dispatcher OnProgress
OnProgress_Internal(35);
TranscodingFillInfo.PCMInfo.RawPCMDataSize = static_cast<uint32>(TranscodingFillInfo.PCMInfo.
RawPCMNumOfFrames * pFlac->channels * sizeof(int16_t));
TranscodingFillInfo.SoundWaveBasicInfo.Duration = static_cast<float>(pFlac->totalPCMFrameCount) / pFlac->
sampleRate;
TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum = pFlac->channels;
TranscodingFillInfo.SoundWaveBasicInfo.SampleRate = pFlac->sampleRate;
drflac_close(pFlac);
return true;
}
default:
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::InvalidAudioFormat);
return false;
break;
}
}
}
bool URuntimeAudioImporterLibrary::TranscodePCMToWAVData(void*& WaveData, size_t& WaveDataSize, uint64 framesToWrite,
int16_t*& pSampleData, uint32 channels, uint32 sampleRate)
bool URuntimeAudioImporterLibrary::TranscodePCMToWAVData()
{
drwav wavEncode;
drwav_data_format format;
format.container = drwav_container_riff;
format.format = DR_WAVE_FORMAT_PCM;
format.channels = channels;
format.sampleRate = sampleRate;
format.channels = TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum;
format.sampleRate = TranscodingFillInfo.SoundWaveBasicInfo.SampleRate;
format.bitsPerSample = 16; // Unreal Engine supports only 16-bit rate
drwav_allocation_callbacks allocationCallbacks;
allocationCallbacks.pUserData = NULL;
allocationCallbacks.onMalloc = unreal_malloc;
allocationCallbacks.onRealloc = unreal_realloc;
allocationCallbacks.onFree = unreal_free;
if (!drwav_init_memory_write(&wavEncode, &WaveData, &WaveDataSize, &format, &allocationCallbacks))
allocationCallbacks.pUserData = nullptr;
allocationCallbacks.onMalloc = Unreal_Malloc;
allocationCallbacks.onRealloc = Unreal_Realloc;
allocationCallbacks.onFree = Unreal_Free;
// Initializing PCM data writing to memory
if (!drwav_init_memory_write(&wavEncode, reinterpret_cast<void**>(&TranscodingFillInfo.WAVInfo.WaveData),
reinterpret_cast<size_t*>(&TranscodingFillInfo.WAVInfo.WaveDataSize), &format,
&allocationCallbacks))
{
return false; //error
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::PCMToWavDataError);
return false;
}
// Callback Dispatcher OnProgress (not completely accurate implementation)
// Callback Dispatcher OnProgress
OnProgress_Internal(40);
drwav_write_pcm_frames(&wavEncode, framesToWrite, pSampleData);
// Write Raw PCM Data
drwav_write_pcm_frames(&wavEncode, TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames,
reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
// Callback Dispatcher OnProgress (not completely accurate implementation)
// Callback Dispatcher OnProgress
OnProgress_Internal(60);
drwav_uninit(&wavEncode);
return true;
}
TEnumAsByte<AudioFormat> URuntimeAudioImporterLibrary::GetAudioFormat(const FString& filePath)
TEnumAsByte<EAudioFormat> URuntimeAudioImporterLibrary::GetAudioFormat(const FString& FilePath)
{
if (FPaths::GetExtension(filePath, false).Equals(TEXT("mp3"), ESearchCase::IgnoreCase))
if (FPaths::GetExtension(FilePath, false).Equals(TEXT("mp3"), ESearchCase::IgnoreCase))
{
return AudioFormat::MP3;
return EAudioFormat::Mp3;
}
else if (FPaths::GetExtension(filePath, false).Equals(TEXT("wav"), ESearchCase::IgnoreCase))
if (FPaths::GetExtension(FilePath, false).Equals(TEXT("wav"), ESearchCase::IgnoreCase))
{
return AudioFormat::WAV;
return EAudioFormat::Wav;
}
else if (FPaths::GetExtension(filePath, false).Equals(TEXT("flac"), ESearchCase::IgnoreCase))
if (FPaths::GetExtension(FilePath, false).Equals(TEXT("flac"), ESearchCase::IgnoreCase))
{
return AudioFormat::FLAC;
return EAudioFormat::Flac;
}
return EAudioFormat::Invalid;
}
FSoundQualityInfo URuntimeAudioImporterLibrary::GenerateSoundQualityInfo(USoundWave* SoundWaveToGetData) const
{
FSoundQualityInfo SoundQualityInfo;
SoundQualityInfo.Duration = SoundWaveToGetData->Duration;
SoundQualityInfo.Quality = SoundWaveToGetData->CompressionQuality;
SoundQualityInfo.NumChannels = SoundWaveToGetData->NumChannels;
// since we cannot get the Sample Rate from the SoundWave, we will get it from the data of the initial PCM transcoding
SoundQualityInfo.SampleRate = TranscodingFillInfo.SoundWaveBasicInfo.SampleRate;
SoundQualityInfo.SampleDataSize = SoundWaveToGetData->RawPCMDataSize;
SoundQualityInfo.bStreaming = SoundWaveToGetData->IsStreaming(*FPlatformCompressionUtilities::GetCookOverrides());
SoundQualityInfo.DebugName = SoundWaveToGetData->GetFullName();
return SoundQualityInfo;
}
FName URuntimeAudioImporterLibrary::GetPlatformSpecificFormat(const FName Format)
{
const FPlatformAudioCookOverrides* CompressionOverrides = FPlatformCompressionUtilities::GetCookOverrides();
// Platforms that require compression overrides get concatenated formats.
#if WITH_EDITOR
FName PlatformSpecificFormat;
if (CompressionOverrides)
{
FString HashedString = *Format.ToString();
FPlatformAudioCookOverrides::GetHashSuffix(CompressionOverrides, HashedString);
PlatformSpecificFormat = *HashedString;
}
else
{
return AudioFormat::INVALID;
PlatformSpecificFormat = Format;
}
#else
// Cache the concatenated hash:
static FName PlatformSpecificFormat;
static FName CachedFormat;
if (!Format.IsEqual(CachedFormat))
{
if (CompressionOverrides)
{
FString HashedString = *Format.ToString();
FPlatformAudioCookOverrides::GetHashSuffix(CompressionOverrides, HashedString);
PlatformSpecificFormat = *HashedString;
}
else
{
PlatformSpecificFormat = Format;
}
CachedFormat = Format;
}
#endif // WITH_EDITOR
return PlatformSpecificFormat;
}
void URuntimeAudioImporterLibrary::OnProgress_Internal(int32 Percentage)
@@ -487,11 +635,12 @@ void URuntimeAudioImporterLibrary::OnProgress_Internal(int32 Percentage)
});
}
void URuntimeAudioImporterLibrary::OnResult_Internal(USoundWave* ReadySoundWave, TEnumAsByte<TranscodingStatus> Status)
void URuntimeAudioImporterLibrary::OnResult_Internal(USoundWave* ReadySoundWave,
const TEnumAsByte<ETranscodingStatus>& Status)
{
AsyncTask(ENamedThreads::GameThread, [=]()
{
RemoveFromRoot();
OnResult.Broadcast(ReadySoundWave, Status);
OnResult.Broadcast(this, ReadySoundWave, Status);
});
}
@@ -0,0 +1,202 @@
// Georgy Treshchev 2021.
#include "VorbisCompressor.h"
#include "Interfaces/IAudioFormat.h"
#include "Serialization/MemoryWriter.h"
#include "VorbisAudioInfo.h"
#if PLATFORM_SUPPORTS_VORBIS_CODEC
THIRD_PARTY_INCLUDES_START
#include "vorbis/vorbisenc.h"
#include "vorbis/vorbisfile.h"
THIRD_PARTY_INCLUDES_END
#endif
#define VORBIS_QUALITY_MODIFIER 0.85
#define SAMPLES_TO_READ 1024
#define SAMPLE_SIZE ( ( uint32 )sizeof( short ) )
bool FVorbisCompressor::GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& CompressedDataStore)
{
#if WITH_OGGVORBIS
{
short ReadBuffer[SAMPLES_TO_READ * SAMPLE_SIZE * 2];
ogg_stream_state Stream_State;
ogg_page Ogg_Page;
ogg_packet Ogg_Packet;
vorbis_info Vorbis_Info;
vorbis_comment Vorbis_Comment;
vorbis_dsp_state Dsp_State;
vorbis_block Vorbis_Block;
uint32 i;
bool bEndOfBitstream;
CompressedDataStore.Empty();
FMemoryWriter CompressedData(CompressedDataStore);
uint32 BufferOffset = 0;
float CompressionQuality = (float)(QualityInfo.Quality * VORBIS_QUALITY_MODIFIER) / 100.0f;
CompressionQuality = FMath::Clamp(CompressionQuality, -0.1f, 1.0f);
vorbis_info_init(&Vorbis_Info);
if (vorbis_encode_init_vbr(&Vorbis_Info, QualityInfo.NumChannels, QualityInfo.SampleRate, CompressionQuality))
{
return false;
}
vorbis_comment_init( &Vorbis_Comment );
vorbis_comment_add_tag( &Vorbis_Comment, "ENCODER", "RuntimeAudioImporter" );
vorbis_analysis_init(&Dsp_State, &Vorbis_Info);
vorbis_block_init(&Dsp_State, &Vorbis_Block);
ogg_stream_init(&Stream_State, 0);
ogg_packet header;
ogg_packet header_comm;
ogg_packet header_code;
vorbis_analysis_headerout(&Dsp_State, &Vorbis_Comment, &header, &header_comm, &header_code);
ogg_stream_packetin(&Stream_State, &header);
ogg_stream_packetin(&Stream_State, &header_comm);
ogg_stream_packetin(&Stream_State, &header_code);
while (true)
{
int result = ogg_stream_flush(&Stream_State, &Ogg_Page);
if (result == 0)
{
break;
}
CompressedData.Serialize(Ogg_Page.header, Ogg_Page.header_len);
CompressedData.Serialize(Ogg_Page.body, Ogg_Page.body_len);
}
bEndOfBitstream = false;
while (!bEndOfBitstream)
{
uint32 BytesToRead = FMath::Min(SAMPLES_TO_READ * QualityInfo.NumChannels * SAMPLE_SIZE,
QualityInfo.SampleDataSize - BufferOffset);
FMemory::Memcpy(ReadBuffer, SrcBuffer.GetData() + BufferOffset, BytesToRead);
BufferOffset += BytesToRead;
if (BytesToRead == 0)
{
vorbis_analysis_wrote(&Dsp_State, 0);
}
else
{
float** buffer = vorbis_analysis_buffer(&Dsp_State, SAMPLES_TO_READ);
if (QualityInfo.NumChannels == 1)
{
for (i = 0; i < BytesToRead / SAMPLE_SIZE; i++)
{
buffer[0][i] = (ReadBuffer[i]) / 32768.0f;
}
}
else
{
for (i = 0; i < BytesToRead / (SAMPLE_SIZE * 2); i++)
{
buffer[0][i] = (ReadBuffer[i * 2]) / 32768.0f;
buffer[1][i] = (ReadBuffer[i * 2 + 1]) / 32768.0f;
}
}
vorbis_analysis_wrote(&Dsp_State, i);
}
while (vorbis_analysis_blockout(&Dsp_State, &Vorbis_Block) == 1)
{
vorbis_analysis(&Vorbis_Block, NULL);
vorbis_bitrate_addblock(&Vorbis_Block);
while (vorbis_bitrate_flushpacket(&Dsp_State, &Ogg_Packet))
{
ogg_stream_packetin(&Stream_State, &Ogg_Packet);
while (!bEndOfBitstream)
{
int result = ogg_stream_pageout(&Stream_State, &Ogg_Page);
if (result == 0)
{
break;
}
CompressedData.Serialize(Ogg_Page.header, Ogg_Page.header_len);
CompressedData.Serialize(Ogg_Page.body, Ogg_Page.body_len);
if (ogg_page_eos(&Ogg_Page))
{
bEndOfBitstream = true;
}
}
}
}
}
ogg_stream_clear(&Stream_State);
vorbis_block_clear(&Vorbis_Block);
vorbis_dsp_clear(&Dsp_State);
vorbis_comment_clear(&Vorbis_Comment);
vorbis_info_clear(&Vorbis_Info);
}
return CompressedDataStore.Num() > 0;
#else
return false;
#endif // WITH_OGGVOBVIS
}
TArray<int32> FVorbisCompressor::GetChannelOrder(int32 NumChannels)
{
TArray<int32> ChannelOrder;
switch (NumChannels)
{
case 6:
{
for (int32 i = 0; i < NumChannels; i++)
{
ChannelOrder.Add(VorbisChannelInfo::Order[NumChannels - 1][i]);
}
break;
}
default:
{
for (int32 i = 0; i < NumChannels; i++)
{
ChannelOrder.Add(i);
}
break;
}
}
return ChannelOrder;
}
int32 FVorbisCompressor::GetMinimumSizeForInitialChunk(FName Format, const TArray<uint8>& SrcBuffer)
{
return 6 * 1024;
}
void FVorbisCompressor::AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize)
{
TArray<uint8>& NewBuffer = *new(OutBuffers) TArray<uint8>;
NewBuffer.Empty(ChunkReserveSize);
}
void FVorbisCompressor::AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkDataSize)
{
TArray<uint8>& TargetBuffer = OutBuffers[OutBuffers.Num() - 1];
TargetBuffer.Append(ChunkData, ChunkDataSize);
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ContentModelStore">
<e p="C:\Users\sampl\AppData\Local\JetBrains\Rider2020.3\extResources" t="IncludeRecursive" />
<e p="C:\Users\sampl\AppData\Local\JetBrains\Rider2020.3\resharper-host\Local\Transient\Rider\v203\SolutionCaches\_.19.00" t="ExcludeRecursive" />
<e p="D:\UE4\Projects\RuntimeAudioImporter\github\RuntimeAudioImporter\Source\RuntimeAudioImporter\Public" t="IncludeFlat" />
</component>
</project>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ContentModelUserStore">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.Public.dir/.idea/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.Public.dir/.idea/riderModule.iml" />
</modules>
</component>
</project>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RiderProjectSettingsUpdater">
<option name="vcsConfiguration" value="2" />
</component>
</project>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RIDER_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$/../.." />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../../.." vcs="Git" />
</component>
</project>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PropertiesComponent">
<property name="settings.editor.selected.configurable" value="preferences.keymap" />
</component>
</project>
@@ -0,0 +1,25 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
struct FSoundQualityInfo;
struct RiffDataChunk
{
uint32 ID;
uint32 DataSize;
uint8* Data;
};
class RUNTIMEAUDIOIMPORTER_API FAdvancedPCMCompressor
{
public:
static bool GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& OutBuffer);
static void AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize);
static void AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkDataSize);
};
@@ -0,0 +1,28 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
struct FSoundQualityInfo;
class RUNTIMEAUDIOIMPORTER_API FOpusCompressor
{
public:
static bool GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& OutBuffer);
static uint16 GetBestOutputSampleRate(int32 SampleRate);
static bool ResamplePCM(uint32 NumChannels, const TArray<uint8>& InBuffer, uint32 InSampleRate,
TArray<uint8>& OutBuffer, uint32 OutSampleRate);
static int32 GetBitRateFromQuality(FSoundQualityInfo& QualityInfo);
static void SerializeHeaderData(FMemoryWriter& CompressedData, uint16 SampleRate, uint32 TrueSampleCount,
uint8 NumChannels, uint16 NumFrames);
static void SerialiseFrameData(FMemoryWriter& CompressedData, uint8* FrameData, uint16 FrameSize);
static int32 AddDataChunk(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkSize);
};
@@ -0,0 +1,35 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
#include "PreimportedSoundAsset.generated.h"
/**
* Pre-imported asset which collects MP3 audio data. Used if you want to load the MP3 file into the editor in advance
*/
UCLASS(BlueprintType, Category = "RuntimeAudioImporter")
class RUNTIMEAUDIOIMPORTER_API UPreimportedSoundAsset : public UObject
{
GENERATED_BODY()
public:
/** Audio data array. Only MP3 data is intended to be here */
UPROPERTY()
TArray<uint8> AudioDataArray;
/** Information about the basic details of an audio file. Used only for convenience in the editor */
#if WITH_EDITORONLY_DATA
UPROPERTY(Category = FilePath, VisibleAnywhere, Meta = (DisplayName = "Source file path"))
FString SourceFilePath;
UPROPERTY(Category = Info, VisibleAnywhere, Meta = (DisplayName = "Sound duration"))
FString SoundDuration;
UPROPERTY(Category = Info, VisibleAnywhere, Meta = (DisplayName = "Number of channels"))
int32 NumberOfChannels;
UPROPERTY(Category = Info, VisibleAnywhere, Meta = (DisplayName = "Sample rate"))
int32 SampleRate;
#endif
};
@@ -1,190 +1,358 @@
// Respirant 2020.
// Georgy Treshchev 2021.
#pragma once
#include "Sound/SoundWave.h"
#include "Async/Async.h"
#include "PreimportedSoundAsset.h"
#include "RuntimeAudioImporterLibrary.generated.h"
/**
* Possible audio importing results
*/
/** Possible audio importing results */
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
enum TranscodingStatus
enum ETranscodingStatus
{
SuccessTranscoding UMETA(DisplayName = "Success"),
FailedToOpenStream UMETA(DisplayName = "Failed to open stream"),
UnsupportedBitDepth UMETA(DisplayName = "Unsupported bit depth (Unreal Engine only supports 16 bit)"),
InvalidWAVEData UMETA(DisplayName = "Invalid WAVE data"),
InvalidBitrate UMETA(DisplayName = "Invalid bitrate (Actual bit rate does not match the container size)"),
InvalidSubformat UMETA(DisplayName = "InvalidSubformat (Subformat identifier not recognized)"),
UnrecognizedReadWaveError UMETA(DisplayName = "Unrecognized ReadWave error"),
USoundWaveDeclarationError UMETA(DisplayName = "USoundWave declaration error"),
InvalidUSoundWave UMETA(DisplayName = "Invalid USoundWave (The USoundWave object contains incorrect data)"),
InvalidAudioFormat UMETA(DisplayName = "Invalid audio format (Can't determine the format of the audio file)"),
AudioDoesNotExist UMETA(DisplayName = "The audio file does not exist"),
ZeroDurationError UMETA(DisplayName = "Transcoded audio has zero length")
/** Success importing */
SuccessImporting UMETA(DisplayName = "Success Importing"),
/** Failed to read Audio Data Array */
FailedToReadAudioDataArray UMETA(DisplayName = "Failed to read Audio Data Array"),
/** SoundWave declaration error */
SoundWaveDeclarationError UMETA(DisplayName = "SoundWave declaration error"),
/** Invalid audio format (Can't determine the format of the audio file) */
InvalidAudioFormat UMETA(DisplayName = "Invalid audio format"),
/** The audio file does not exist */
AudioDoesNotExist UMETA(DisplayName = "Audio does not exist"),
/** Load file to array error */
LoadFileToArrayError UMETA(DisplayName = "Load file to array error"),
/** Transcoding PCM to Wav data error */
PCMToWavDataError UMETA(DisplayName = "PCM to Wav data error"),
/** Generate compressed data error */
GenerateCompressedDataError UMETA(DisplayName = "Generate compressed data error"),
/** Invalid compressed format error */
InvalidCompressedFormat UMETA(DisplayName = "Invalid compressed format")
};
/**
* Possible audio formats
*/
/** Possible audio formats (extensions) */
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
enum AudioFormat
enum EAudioFormat
{
MP3 UMETA(DisplayName = "mp3"),
WAV UMETA(DisplayName = "wav"),
FLAC UMETA(DisplayName = "flac"),
INVALID UMETA(DisplayName = "invalid (not defined format, internal use only)", Hidden)
/** Determine format automatically */
Auto UMETA(DisplayName = "Determine format automatically"),
/** MP3 format */
Mp3 UMETA(DisplayName = "mp3"),
/** WAV format */
Wav UMETA(DisplayName = "wav"),
/** FLAC format */
Flac UMETA(DisplayName = "flac"),
/** Invalid format */
Invalid UMETA(DisplayName = "invalid (not defined format, CPP use only)", Hidden)
};
/** List of all supported compression formats */
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
enum ECompressionFormat
{
/** Raw (Wav) data format. Not recommended because it doesn't work on packaged build */
RawData UMETA(DisplayName = "Raw Data"),
/** Ogg Vorbis format. It is used, for example, on Android and Windows */
OggVorbis UMETA(DisplayName = "Ogg Vorbis"),
/** ADPCM or LPCM formats. LPCM will be used if the compression quality is 100%, otherwise ADPCM */
ADPCMLPCM UMETA(DisplayName = "ADPCM/LPCM"),
/** Ogg Opus format. Mostly not used anywhere, but can be used for personal purposes */
OggOpus UMETA(DisplayName = "Ogg Opus")
};
/** Information about which buffers to fill */
USTRUCT(BlueprintType)
struct FBuffersDetailsStruct
{
GENERATED_BODY()
/** Needed compression format */
UPROPERTY(BlueprintReadWrite)
TEnumAsByte<ECompressionFormat> CompressionFormat = OggVorbis;
/**
* Sound compression quality. The range of numbers is from 0 to 100, in percent. How much to compress the sound, where 0 is the maximum loss, 100 is the minimum.
* Not used if CompressionFormat is "RawData"
*/
UPROPERTY(BlueprintReadWrite)
int32 SoundCompressionQuality = 100;
};
/** Basic SoundWave data. CPP use only. */
struct FSoundWaveBasicStruct
{
/** Number of channels */
int32 ChannelsNum;
/** Sample rate (samples per second, sampling frequency) */
uint32 SampleRate;
/** Sound wave duration, sec */
float Duration;
};
/** Raw PCM Data */
struct FPCMStruct
{
/** Raw PCM data pointer */
uint8* RawPCMData;
/** Number of PCM frames */
uint64 RawPCMNumOfFrames;
/** Raw PCM data size */
uint32 RawPCMDataSize;
};
/** Wav (or Raw) Data */
struct FWAVStruct
{
/** Wave data pointer */
uint8* WaveData;
/** Wave data size */
int32 WaveDataSize;
};
/** Compressed Data (Ogg Vorbis, ADPCM, etc) */
struct FCompressedStruct
{
/** Array, which contains compressed format data */
TArray<uint8> CompressedFormatData;
};
/** Main, mostly in-memory information (like PCM, Wav, etc) */
struct FTranscodingFillStruct
{
/** SoundWave basic info (e.g. duration, number of channels, etc) */
FSoundWaveBasicStruct SoundWaveBasicInfo;
/** Raw PCM Data info */
FPCMStruct PCMInfo;
/** Wav (or Raw) Data info */
FWAVStruct WAVInfo;
/** Compressed Data info (Ogg Vorbis, ADPCM, etc) */
FCompressedStruct CompressedInfo;
};
// Forward declaration of the FSoundQualityInfo structure
struct FSoundQualityInfo;
/**
* Declare delegate which will be called during the transcoding process
*
* @param Percentage Percentage of download completed (0-100%)
* @note The impementation of dispatcher is not fully right. The data is taken approximately, and will not show exactly the correct percentage of transcoding progress
* @param Percentage Percentage of importing completed (0-100%)
*/
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnProgress, const int32, Percentage);
/**
* Declare a delegate that will be called on the transcoding result
*
* @param ReadySoundWave Ready USoundWave Object
* @param Status Reference to TranscodingStatus Enumerator in case an error occurs
*
* @param RuntimeAudioImporterObject Runtime Audio Importer object reference
* @param ReadySoundWave Ready SoundWave object reference
* @param Status TranscodingStatus Enum in case an error occurs
*/
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnResult, const USoundWave*, ReadySoundWave,
const TEnumAsByte < TranscodingStatus >, Status);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnResult, class URuntimeAudioImporterLibrary*,
RuntimeAudioImporterObjectRef,
const USoundWave*, SoundWaveRef,
const TEnumAsByte < ETranscodingStatus >&, Status);
UCLASS(BlueprintType, Category = "RuntimeFilesDownloader")
/**
* Runtime Audio Importer object
* Allows you to do various things with audio files in real time, for example, import audio files into a SoundWave engine object
*/
UCLASS(BlueprintType, Category = "RuntimeAudioImporter")
class RUNTIMEAUDIOIMPORTER_API URuntimeAudioImporterLibrary : public UObject
{
GENERATED_UCLASS_BODY()
GENERATED_BODY()
public:
/**
* Bind to know when the transcoding is on progress.
*/
/** Bind to know when the transcoding is on progress */
UPROPERTY(BlueprintAssignable, Category = "RuntimeAudioImporter")
FOnProgress OnProgress;
/**
* Bind to know when the transcoding is complete (even if it fails).
*/
/** Bind to know when the transcoding is complete (even if it fails) */
UPROPERTY(BlueprintAssignable, Category = "RuntimeAudioImporter")
FOnResult OnResult;
/** Transcoding fill info. CPP use only */
FTranscodingFillStruct TranscodingFillInfo = FTranscodingFillStruct();
/** Buffers details info. CPP use only */
FBuffersDetailsStruct BuffersDetailsInfo = FBuffersDetailsStruct();
/**
* Instantiates a RuntimeAudioImporter object.
* Instantiates a RuntimeAudioImporter object
*
* @return The RuntimeAudioImporter object. Bind to it's OnProgress and OnResult events to know when it is in the process of transcoding and has been transcoded.
* @return The RuntimeAudioImporter object. Bind to it's OnProgress and OnResult events to know when it is in the process of transcoding and imported
* @note You must place the returned reference to the RuntimeAudioImporterLibrary in a separate variable so that the UObject is not removed during garbage collection
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Create, Audio, Runtime, MP3, FLAC, WAV"), Category =
"RuntimeAudioImporter")
static URuntimeAudioImporterLibrary* CreateRuntimeAudioImporter();
/**
* Transcode audio file to USoundWave static object
* Transcode audio file to SoundWave object
*
* @param FilePath Path to the audio file to import
* @param Format Audio file format (extension)
* @param DefineFormatAutomatically Whether to define format (extension) automatically or not
* @return Returns itself.
* @param FilePath Path to the audio file to import
* @param Format Audio file format (extension)
* @param BuffersDetails Information about which buffers to fill
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV"),
Category = "RuntimeAudioImporter")
URuntimeAudioImporterLibrary* ImportAudioFromFile(const FString& FilePath, TEnumAsByte<AudioFormat> Format,
bool DefineFormatAutomatically);
void ImportAudioFromFile(const FString& FilePath, TEnumAsByte<EAudioFormat> Format,
const FBuffersDetailsStruct& BuffersDetails);
/**
* Destroy USoundWave static object. After the Soundwave is no longer needed, you need to call this function to free memory
* Import audio file from the preimported sound asset
*
* @param ReadySoundWave SoundWave Object need to be destroyed
* @return Whether the descruction of USoundWave was successful or not
* @param PreimportedSoundAssetRef PreimportedSoundAsset object reference. Should contains "BaseAudioDataArray" buffer
* @param BuffersDetails Information about which buffers to fill
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3"),
Category = "RuntimeAudioImporter")
void ImportAudioFromPreimportedSound(UPreimportedSoundAsset* PreimportedSoundAssetRef,
const FBuffersDetailsStruct& BuffersDetails);
/**
* Transcode audio file to USoundWave static object
*
* @param AudioDataArray Array of Audio byte data
* @param Format Audio file format (extension)
* @param BuffersDetails Information about which buffers to fill
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV"),
Category = "RuntimeAudioImporter")
void ImportAudioFromBuffer(const TArray<uint8>& AudioDataArray, const TEnumAsByte<EAudioFormat>& Format,
const FBuffersDetailsStruct& BuffersDetails);
/**
* Destroy SoundWave object. After the SoundWave is no longer needed, you need to call this function to free memory
*
* @param ReadySoundWave SoundWave object reference need to be destroyed
* @return Whether the destruction of the SoundWave was successful or not
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "MP3, FLAC, WAV, Destroy"), Category = "RuntimeAudioImporter")
static bool DestroySoundWave(USoundWave* ReadySoundWave);
/**
* Export (save) SoundWave object to .WAV audio file to device memory. So far beta and it is not recommended to use due to the fact that the engine can clear the sound data itself
*
* @param SoundWaveToExport SoundWave object need to be exported
* @param PathToExport Path where to save audio file
* @return Whether the save was successfull or not
* @warning It is very important that USoundWave should not used by the engine so that it can be read from physical memory.
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Exporter, Export, Save, Runtime, WAV"), Category =
"RuntimeAudioImporter")
static bool ExportSoundWaveToFile(USoundWave* SoundWaveToExport, FString PathToExport);
private:
/**
* Internal main audio transcoding function
*
* @param FilePath Path to the audio file to import
* @param Format Audio file format (extension)
* @param DefineFormatAutomatically Whether to define format (extension) automatically or not
* @return Returns itself
* @param AudioDataArray Array of Audio byte data
* @param Format Audio file format (extension)
* @param BuffersDetails Information about which buffers to fill
*/
URuntimeAudioImporterLibrary* GetUSoundWaveFromAudioFile_Internal(const FString& FilePath,
TEnumAsByte<AudioFormat> Format,
bool DefineFormatAutomatically);
void ImportAudioFromBuffer_Internal(const TArray<uint8>& AudioDataArray,
const TEnumAsByte<EAudioFormat>& Format,
const FBuffersDetailsStruct& BuffersDetails);
/**
* Get USoundWave static object reference from 16-bit WAV data
* Define SoundWave object reference
*
* @param WaveData Reference to memory location of 16-bit WAV byte data
* @param WaveDataSize Memory size allocated for 16-bit WAV byte data
* @param Status Reference to TranscodingStatus Enumerator in case an error occurs
* @return Returns a ready USoundWave object
* @return Returns a ready USoundWave object
*/
class USoundWave* GetSoundWaveObject(const uint8* WaveData, int32 WaveDataSize,
TEnumAsByte<TranscodingStatus>& Status);
USoundWave* DefineSoundWave();
/**
* Transcode Audio from File to PCM Data
* Fill SoundWave basic information (e.g. duration, number of channels, etc)
*
* @param FilePath Path to the audio file from which to receive PCM data
* @param Format Format of the audio file (e.g. mp3. flac, etc)
* @param Status Reference to TranscodingStatus Enumerator in case an error occurs
* @param framesToWrite PCM frames ready to be written
* @param pSampleData Ready PCM sample data for which memory has been allocated
* @param channels Quantity of channels (e.g. 1, 2, etc)
* @param sampleRate Sample rate of the audio
* @return Whether the transcoding was successful or not
* @param SoundWaveRef SoundWave object reference
*/
bool TranscodeAudioFileToPCMData(FString FilePath, TEnumAsByte<AudioFormat> Format,
TEnumAsByte<TranscodingStatus>& Status, uint64& framesToWrite,
int16_t* & pSampleData, uint32& channels, uint32& sampleRate);
void FillSoundWaveBasicInfo(USoundWave* SoundWaveRef) const;
/**
* Fill SoundWave Raw (Wave) data buffer
*
* @param SoundWaveRef SoundWave object reference
*/
void FillRawWaveData(USoundWave* SoundWaveRef) const;
/**
* Fill SoundWave compressed data buffer
*
* @param SoundWaveRef SoundWave object reference
*/
void FillCompressedData(USoundWave* SoundWaveRef);
/**
* Transcode Audio from PCM to 16-bit WAV data
*
* @param WaveData Reference to memory location of 16-bit WAV byte data
* @param WaveDataSize Memory size allocated for 16-bit WAV byte data
* @param framesToWrite PCM frames which will be written for WAV data
* @param pSampleData Ready WAV sample data for which memory has been allocated
* @param channels Quantity of channels (e.g. 1, 2, etc)
* @param sampleRate Sample rate of the audio
* @return Whether the transcoding was successful or not
* @return Whether the transcoding was successful or not
*/
bool TranscodePCMToWAVData(void*& WaveData, size_t& WaveDataSize, uint64 framesToWrite, int16_t* & pSampleData,
uint32 channels, uint32 sampleRate);
bool TranscodePCMToCompressedData(USoundWave* SoundWaveToGetData);
/**
* @param FilePath File path, where to find the format (by extension)
* @return Returns the found audio format (e.g. mp3. flac, etc) by AudioFormat Enumerator
* Transcode Audio from Audio Data to PCM Data
*
* @param AudioData Pointer to memory location of Audio byte data
* @param AudioDataSize Memory size allocated for Audio byte data
* @param Format Format of the audio file (e.g. mp3. flac, etc)
* @return Whether the transcoding was successful or not
*/
static TEnumAsByte<AudioFormat> GetAudioFormat(const FString& FilePath);
bool TranscodeAudioDataArrayToPCMData(const uint8* AudioData, uint32 AudioDataSize,
TEnumAsByte<EAudioFormat> Format);
/**
* Transcode Audio from PCM to 16-bit WAV data
*
* @return Whether the transcoding was successful or not
*/
bool TranscodePCMToWAVData();
/**
* Get Audio Format by extension
*
* @param FilePath File path, where to find the format (by extension)
* @return Returns the found audio format (e.g. mp3. flac, etc) by EAudioFormat Enum
*/
UFUNCTION(BlueprintCallable, Category = "RuntimeAudioImporter")
static TEnumAsByte<EAudioFormat> GetAudioFormat(const FString& FilePath);
/**
* Generate FSoundQualityInfo Enum based on SoundWave and already defined Sample Rate
*/
FSoundQualityInfo GenerateSoundQualityInfo(USoundWave* SoundWaveToGetData) const;
/**
* Get the platform-specific format name based on the base format to find the compressed buffer to fill
*
* @param Format Base format name (it can be "OGG", "LPCM", etc)
*/
static FName GetPlatformSpecificFormat(const FName Format);
/**
* Audio transcoding progress callback
* @param Percentage Percentage of download completion (0-100%)
* @note The impementation of dispatcher is not fully right. The data is taken approximately, and will not show exactly the correct percentage of transcoding progress
* @param Percentage Percentage of importing completion (0-100%)
*/
void OnProgress_Internal(int32 Percentage);
/**
* Audio transcoding finished callback
* @param ReadySoundWave A ready USoundWave object
* @param Status Reference to TranscodingStatus Enumerator
*
* @param ReadySoundWave A ready SoundWave object
* @param Status TranscodingStatus Enum in case an error occurs
*/
void OnResult_Internal(USoundWave* ReadySoundWave, TEnumAsByte<TranscodingStatus> Status);
void OnResult_Internal(USoundWave* ReadySoundWave, const TEnumAsByte<ETranscodingStatus>& Status);
};
@@ -0,0 +1,22 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
struct FSoundQualityInfo;
class RUNTIMEAUDIOIMPORTER_API FVorbisCompressor
{
public:
static bool GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& OutBuffer);
static int32 GetMinimumSizeForInitialChunk(FName Format, const TArray<uint8>& SrcBuffer);
static TArray<int32> GetChannelOrder(int32 NumChannels);
static void AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize);
static void AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkDataSize);
};
@@ -1,4 +1,4 @@
// Respirant 2020.
// Georgy Treshchev 2021.
using System;
using System.IO;
using UnrealBuildTool;
@@ -9,7 +9,9 @@ public class RuntimeAudioImporter : ModuleRules
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDefinitions.AddRange(
//PublicDefinitions.Add("WITH_OGGVORBIS=0");
PublicDefinitions.AddRange(
new string[] {
"DR_WAV_IMPLEMENTATION=1",
"DR_MP3_IMPLEMENTATION=1",
@@ -21,8 +23,7 @@ public class RuntimeAudioImporter : ModuleRules
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
"Core"
}
);
@@ -34,16 +35,21 @@ public class RuntimeAudioImporter : ModuleRules
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
"AudioPlatformConfiguration"
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
AddEngineThirdPartyPrivateStaticDependencies(Target,
"UEOgg",
"Vorbis",
"libOpus"
);
PublicDefinitions.Add("WITH_OGGVORBIS");
}
}
@@ -0,0 +1,121 @@
// Georgy Treshchev 2021.
#include "PreimportedSoundFactory.h"
#include "PreimportedSoundAsset.h" //maybe #include "RuntimeAudioImporter/Public/PreimportedSoundAsset.h"
#include "Misc/FileHelper.h"
/** Defining custom logging */
DEFINE_LOG_CATEGORY(LogPreimportedSoundFactory)
#include "ThirdParty/dr_mp3.h"
/**
* Replacing standard CPP memory functions (malloc, realloc, free) with memory management functions that are maintained by Epic as the engine evolves.
*/
void* Unreal_Malloc(size_t sz, void* pUserData)
{
return FMemory::Malloc(sz);
}
void* Unreal_Realloc(void* p, size_t sz, void* pUserData)
{
return FMemory::Realloc(p, sz);
}
void Unreal_Free(void* p, void* pUserData)
{
FMemory::Free(p);
}
UPreimportedSoundFactory::UPreimportedSoundFactory()
{
Formats.Add(TEXT("mp3;MP3 Preimported Audio Format"));
Formats.Add(TEXT("imp;IMP (the same as MP3) Preimported Audio Format"));
SupportedClass = UPreimportedSoundAsset::StaticClass();
bCreateNew = false; // turned off for import
bEditAfterNew = false; // turned off for import
bEditorImport = true;
bText = false;
}
bool UPreimportedSoundFactory::FactoryCanImport(const FString& Filename)
{
return FPaths::GetExtension(Filename).Equals(TEXT("mp3")) || FPaths::GetExtension(Filename).Equals(TEXT("imp"));
}
UObject* UPreimportedSoundFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName,
EObjectFlags Flags, const FString& Filename, const TCHAR* Parms,
FFeedbackContext* Warn, bool& bOutOperationCanceled)
{
UPreimportedSoundAsset* PreimportedSoundAsset = nullptr;
TArray<uint8> AudioDataArray;
if (FFileHelper::LoadFileToArray(AudioDataArray, *Filename))
{
drmp3_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = nullptr;
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
allocationCallbacksDecoding.onFree = Unreal_Free;
drmp3 mp3;
if (!drmp3_init_memory(&mp3, AudioDataArray.GetData(), AudioDataArray.Num() - 2, &allocationCallbacksDecoding))
{
UE_LOG(LogPreimportedSoundFactory, Log, TEXT("The file you are trying to import is not an MP3 file."));
return nullptr;
}
PreimportedSoundAsset = NewObject<UPreimportedSoundAsset>(InParent, InClass, InName, Flags);
PreimportedSoundAsset->AudioDataArray = AudioDataArray;
PreimportedSoundAsset->SourceFilePath = Filename;
PreimportedSoundAsset->SoundDuration = ConvertSecToFormattedDuration(
static_cast<int32>(drmp3_get_pcm_frame_count(&mp3)) / mp3.sampleRate);
PreimportedSoundAsset->NumberOfChannels = mp3.channels;
PreimportedSoundAsset->SampleRate = mp3.sampleRate;
drmp3_uninit(&mp3);
}
else
{
UE_LOG(LogPreimportedSoundFactory, Log, TEXT("Unable to read the audio file. Check file permissions."));
return nullptr;
}
bOutOperationCanceled = false;
return PreimportedSoundAsset;
}
FString UPreimportedSoundFactory::ConvertSecToFormattedDuration(int32 Seconds)
{
FString FinalString;
const int32 NewHours = Seconds / 3600;
if (NewHours > 0)
{
FinalString += ((NewHours < 10) ? TEXT("0") + FString::FromInt(NewHours) : FString::FromInt(NewHours)) +
TEXT(":");
}
Seconds = Seconds % 3600;
const int32 NewMinutes = Seconds / 60;
if (NewMinutes > 0)
{
FinalString += ((NewMinutes < 10) ? TEXT("0") + FString::FromInt(NewMinutes) : FString::FromInt(NewMinutes)) +
TEXT(":");
}
const int32 NewSeconds = Seconds % 60;
if (NewSeconds > 0)
{
FinalString += (NewSeconds < 10) ? TEXT("0") + FString::FromInt(NewSeconds) : FString::FromInt(NewSeconds);
}
return FinalString;
}
@@ -0,0 +1,17 @@
// Georgy Treshchev 2021.
#include "RuntimeAudioImporterEditor.h"
#define LOCTEXT_NAMESPACE "FRuntimeAudioImporterEditorModule"
void FRuntimeAudioImporterEditorModule::StartupModule()
{
}
void FRuntimeAudioImporterEditorModule::ShutdownModule()
{
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FRuntimeAudioImporterEditorModule, RuntimeAudioImporterEditor)
@@ -0,0 +1,44 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "PreimportedSoundFactory.generated.h"
/** Declaring custom logging */
DECLARE_LOG_CATEGORY_EXTERN(LogPreimportedSoundFactory, Error, All);
/**
* Factory for pre-importing audio files. Only MP3 is available for pre-import because it takes up the least amount of memory.
*/
UCLASS()
class RUNTIMEAUDIOIMPORTEREDITOR_API UPreimportedSoundFactory : public UFactory
{
GENERATED_BODY()
public:
UPreimportedSoundFactory();
/**
* Check whether the audio file can be imported.
*
* @param Filename The name of the importing audio file
* @note Only MP3 is available for pre-import because it takes up the least amount of memory.
*/
virtual bool FactoryCanImport(const FString& Filename) override;
virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags,
const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn,
bool& bOutOperationCanceled) override;
private:
/**
* Convert sound duration from seconds to formatted string (hh:mm:ss)
*
* @param Seconds Audio duration in seconds
*/
static FString ConvertSecToFormattedDuration(int32 Seconds);
};
@@ -0,0 +1,16 @@
// Respirant 2020.
#pragma once
#include "Modules/ModuleManager.h"
class FRuntimeAudioImporterEditorModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
@@ -0,0 +1,46 @@
// Georgy Treshchev 2021.
using UnrealBuildTool;
public class RuntimeAudioImporterEditor : ModuleRules
{
public RuntimeAudioImporterEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDefinitions.AddRange(
new string[] {
"DR_MP3_IMPLEMENTATION=1"
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"RuntimeAudioImporter"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"UnrealEd"
}
);
}
}
+6 -3
View File
@@ -1,6 +1,6 @@
/*
FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file.
dr_flac - v0.12.27 - 2021-01-31
dr_flac - v0.12.28 - 2021-02-21
David Reid - mackron@gmail.com
@@ -179,7 +179,7 @@ reports metadata to the application through the use of a callback, and every met
The main opening APIs (`drflac_open()`, etc.) will fail if the header is not present. The presents a problem in certain scenarios such as broadcast style
streams or internet radio where the header may not be present because the user has started playback mid-stream. To handle this, use the relaxed APIs:
`drflac_open_relaxed()`
`drflac_open_with_metadata_relaxed()`
@@ -232,7 +232,7 @@ extern "C" {
#define DRFLAC_VERSION_MAJOR 0
#define DRFLAC_VERSION_MINOR 12
#define DRFLAC_VERSION_REVISION 27
#define DRFLAC_VERSION_REVISION 28
#define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION)
#include <stddef.h> /* For size_t. */
@@ -11806,6 +11806,9 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
/*
REVISION HISTORY
================
v0.12.28 - 2021-02-21
- Fix a warning due to referencing _MSC_VER when it is undefined.
v0.12.27 - 2021-01-31
- Fix a static analysis warning.
+8 -5
View File
@@ -1,6 +1,6 @@
/*
MP3 audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file.
dr_mp3 - v0.6.26 - 2021-01-31
dr_mp3 - v0.6.27 - 2021-02-21
David Reid - mackron@gmail.com
@@ -95,7 +95,7 @@ extern "C" {
#define DRMP3_VERSION_MAJOR 0
#define DRMP3_VERSION_MINOR 6
#define DRMP3_VERSION_REVISION 26
#define DRMP3_VERSION_REVISION 27
#define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION)
#include <stddef.h> /* For size_t. */
@@ -3519,7 +3519,7 @@ DRMP3_API void drmp3_uninit(drmp3* pMP3)
if (pMP3 == NULL) {
return;
}
#ifndef DR_MP3_NO_STDIO
if (pMP3->onRead == drmp3__on_read_stdio) {
FILE* pFile = (FILE*)pMP3->pUserData;
@@ -3907,7 +3907,7 @@ DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint
/* We'll need to seek back to where we were, so grab the PCM frame we're currently sitting on so we can restore later. */
currentPCMFrame = pMP3->currentPCMFrame;
if (!drmp3_seek_to_start_of_stream(pMP3)) {
return DRMP3_FALSE;
}
@@ -4005,7 +4005,7 @@ DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pS
/* We'll need to seek back to the current sample after calculating the seekpoints so we need to go ahead and grab the current location at the top. */
currentPCMFrame = pMP3->currentPCMFrame;
/* We never do more than the total number of MP3 frames and we limit it to 32-bits. */
if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, &totalPCMFrameCount)) {
return DRMP3_FALSE;
@@ -4450,6 +4450,9 @@ counts rather than sample counts.
/*
REVISION HISTORY
================
v0.6.27 - 2021-02-21
- Fix a warning due to referencing _MSC_VER when it is undefined.
v0.6.26 - 2021-01-31
- Bring up to date with minimp3.
+70 -74
View File
@@ -1,6 +1,6 @@
/*
WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file.
dr_wav - v0.12.18 - 2021-01-31
dr_wav - v0.12.19 - 2021-02-21
David Reid - mackron@gmail.com
@@ -30,7 +30,7 @@ callback which will give you information about the data format. To determine the
Introduction
============
This is a single file library. To use it, do something like the following in one .c file.
```c
#define DR_WAV_IMPLEMENTATION
#include "dr_wav.h"
@@ -144,7 +144,7 @@ extern "C" {
#define DRWAV_VERSION_MAJOR 0
#define DRWAV_VERSION_MINOR 12
#define DRWAV_VERSION_REVISION 18
#define DRWAV_VERSION_REVISION 19
#define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION)
#include <stddef.h> /* For size_t. */
@@ -424,7 +424,7 @@ Use the `container` argument to discriminate the fields in `pChunkHeader->id`. I
use `id.fourcc`, otherwise you should use `id.guid`.
The `pFMT` parameter can be used to determine the data format of the wave file. Use `drwav_fmt_get_format()` to get the sample format, which will be one of the
`DR_WAVE_FORMAT_*` identifiers.
`DR_WAVE_FORMAT_*` identifiers.
The read pointer will be sitting on the first byte after the chunk's header. You must not attempt to read beyond the boundary of the chunk.
*/
@@ -534,8 +534,8 @@ typedef struct
/* The size in bytes of the data chunk. */
drwav_uint64 dataChunkDataSize;
/* The position in the stream of the first byte of the data chunk. This is used for seeking. */
/* The position in the stream of the first data byte of the data chunk. This is used for seeking. */
drwav_uint64 dataChunkDataPos;
/* The number of bytes remaining in the data chunk. */
@@ -565,7 +565,7 @@ typedef struct
{
drwav_uint64 iCurrentPCMFrame; /* The index of the next PCM frame that will be read by drwav_read_*(). This is used with "totalPCMFrameCount" to ensure we don't read excess samples at the end of the last block. */
} compressed;
/* Microsoft ADPCM specific data. */
struct
{
@@ -1932,7 +1932,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on
We need to enumerate over each chunk for two reasons:
1) The "data" chunk may not be the next one
2) We may want to report each chunk back to the client
In order to correctly report each chunk back to the client we will need to keep looping until the end of the file.
*/
foundDataChunk = DRWAV_FALSE;
@@ -1964,7 +1964,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on
}
}
}
if (!foundDataChunk) {
pWav->dataChunkDataPos = cursor;
@@ -2106,7 +2106,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on
}
cursor = pWav->dataChunkDataPos;
}
/* At this point we should be sitting on the first byte of the raw audio data. */
@@ -2378,7 +2378,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_d
runningPos += drwav__write(pWav, "WAVE", 4);
}
/* "ds64" chunk (RF64 only). */
if (pFormat->container == drwav_container_rf64) {
drwav_uint32 initialds64ChunkSize = 28; /* 28 = [Size of RIFF (8 bytes)] + [Size of DATA (8 bytes)] + [Sample Count (8 bytes)] + [Table Length (4 bytes)]. Table length always set to 0. */
@@ -2411,8 +2411,6 @@ DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_d
runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign);
runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample);
pWav->dataChunkDataPos = runningPos;
/* "data" chunk. */
if (pFormat->container == drwav_container_riff) {
drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize;
@@ -2427,19 +2425,13 @@ DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_d
runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always set to 0xFFFFFFFF for RF64. The true size of the data chunk is specified in the ds64 chunk. */
}
/*
The runningPos variable is incremented in the section above but is left unused which is causing some static analysis tools to detect it
as a dead store. I'm leaving this as-is for safety just in case I want to expand this function later to include other tags and want to
keep track of the running position for whatever reason. The line below should silence the static analysis tools.
*/
(void)runningPos;
/* Set some properties for the client's convenience. */
pWav->container = pFormat->container;
pWav->channels = (drwav_uint16)pFormat->channels;
pWav->sampleRate = pFormat->sampleRate;
pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample;
pWav->translatedFormatTag = (drwav_uint16)pFormat->format;
pWav->dataChunkDataPos = runningPos;
return DRWAV_TRUE;
}
@@ -3238,7 +3230,7 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, dr
return DRWAV_FALSE; /* Trying to seek too far forward. */
}
}
return DRWAV_TRUE;
}
@@ -3307,7 +3299,7 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offs
pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize; /* Trying to seek too far forward. */
}
}
return DRWAV_TRUE;
}
@@ -3399,7 +3391,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav)
} else {
paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize);
}
if (paddingSize > 0) {
drwav_uint64 paddingData = 0;
drwav__write(pWav, &paddingData, paddingSize); /* Byte order does not matter for this. */
@@ -3417,8 +3409,8 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav)
drwav__write_u32ne_to_le(pWav, riffChunkSize);
}
/* the "data" chunk size. */
if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 4, drwav_seek_origin_start)) {
/* The "data" chunk size. */
if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, drwav_seek_origin_start)) {
drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize);
drwav__write_u32ne_to_le(pWav, dataChunkSize);
}
@@ -3430,7 +3422,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav)
}
/* The "data" chunk size. */
if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 16, drwav_seek_origin_start)) {
if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, drwav_seek_origin_start)) {
drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize);
drwav__write_u64ne_to_le(pWav, dataChunkSize);
}
@@ -3609,7 +3601,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav)
DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */
}
}
pWav->bytesRemaining = pWav->dataChunkDataSize;
return DRWAV_TRUE;
}
@@ -3643,7 +3635,7 @@ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetF
*/
if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) {
/* TODO: This can be optimized. */
/*
If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards,
we first need to seek back to the start and then just do the same thing as a forward seek.
@@ -3791,7 +3783,7 @@ DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 frame
pRunningData = (const drwav_uint8*)pData;
bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels;
while (bytesToWrite > 0) {
drwav_uint8 temp[4096];
drwav_uint32 sampleCount;
@@ -3921,9 +3913,9 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav
if (pWav->msadpcm.bytesRemainingInBlock == 0) {
continue;
} else {
static drwav_int32 adaptationTable[] = {
230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230
static drwav_int32 adaptationTable[] = {
230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230
};
static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 };
static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 };
@@ -4030,15 +4022,15 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin
};
static drwav_int32 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,
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,
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
5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
};
DRWAV_ASSERT(pWav != NULL);
@@ -4180,40 +4172,40 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin
#ifndef DR_WAV_NO_CONVERSION_API
static unsigned short g_drwavAlawTable[256] = {
0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580,
0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0,
0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600,
0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00,
0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58,
0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58,
0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960,
0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0,
0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80,
0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40,
0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00,
0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500,
0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8,
0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8,
0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0,
0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580,
0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0,
0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600,
0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00,
0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58,
0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58,
0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960,
0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0,
0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80,
0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40,
0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00,
0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500,
0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8,
0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8,
0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0,
0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350
};
static unsigned short g_drwavMulawTable[256] = {
0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84,
0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84,
0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004,
0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844,
0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64,
0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74,
0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C,
0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000,
0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C,
0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C,
0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC,
0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC,
0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C,
0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C,
0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084,
0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84,
0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84,
0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004,
0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844,
0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64,
0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74,
0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C,
0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000,
0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C,
0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C,
0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC,
0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC,
0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C,
0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C,
0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084,
0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000
};
@@ -4306,14 +4298,14 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uin
if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) {
return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut);
}
bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);
if (bytesPerFrame == 0) {
return 0;
}
totalFramesRead = 0;
while (framesToRead > 0) {
drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);
if (framesRead == 0) {
@@ -4346,7 +4338,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_ui
}
totalFramesRead = 0;
while (framesToRead > 0) {
drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);
if (framesRead == 0) {
@@ -4379,7 +4371,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_ui
}
totalFramesRead = 0;
while (framesToRead > 0) {
drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData);
if (framesRead == 0) {
@@ -4730,7 +4722,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_ui
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) {
return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut);
}
bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);
if (bytesPerFrame == 0) {
return 0;
@@ -5064,7 +5056,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uin
if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) {
return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut);
}
bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav);
if (bytesPerFrame == 0) {
return 0;
@@ -6022,6 +6014,10 @@ two different ways to initialize a drwav object.
/*
REVISION HISTORY
================
v0.12.19 - 2021-02-21
- Fix a warning due to referencing _MSC_VER when it is undefined.
- Minor improvements to the management of some internal state concerning the data chunk cursor.
v0.12.18 - 2021-01-31
- Clean up some static analysis warnings.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 86 KiB