big update: callback dispatchers, memory leak fixes and more

This commit is contained in:
Respirant
2021-01-02 09:53:35 +03:00
parent d543b80f2e
commit f0cc227a33
7 changed files with 608 additions and 375 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@@ -4,37 +4,21 @@
"VersionName": "1.0",
"FriendlyName": "RuntimeAudioImporter",
"Description": "Runtime Audio Importer plugin for Unreal Engine 4. Supports mp3, wav and flac formats.",
"Category": "Code Plugins",
"Category": "Audio",
"CreatedBy": "Respirant",
"CreatedByURL": "https://github.com/Respirant/RuntimeAudioImporter",
"DocsURL": "https://github.com/Respirant/RuntimeAudioImporter",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036",
"MarketplaceURL": "",
"SupportURL": "https://telegram.me/Respirant",
"CanContainContent": false,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": true,
"EngineVersion": "4.26.0",
"Modules": [
{
"Name": "RuntimeAudioImporter",
"Type": "Runtime",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64",
"Win32",
"Mac",
"Android",
"IOS",
"Linux"
],
"BlacklistPlatforms": [
"XboxOne",
"PS4",
"HTML5",
"TVOS",
"Switch"
]
"LoadingPhase": "Default"
}
]
}
}
@@ -1,248 +0,0 @@
// Respirant 2020.
#include "RuntimeAudioImporterBPLibrary.h"
#include "RuntimeAudioImporter.h"
URuntimeAudioImporterBPLibrary::URuntimeAudioImporterBPLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
/**
* Transcode audio file to USoundWave static object
*
* @param filePath Path to the audio file to import
* @param Format Audio file format (extension)
* @param status Final import status (TranscodingStatus)
* @param DefineFormatAutomatically Whether to define format (extension) automatically or not
* @return Returns USoundWave Static Object.
*/
class USoundWave* URuntimeAudioImporterBPLibrary::GetSoundWaveFromAudioFile(const FString& filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus >& status, bool DefineFormatAutomatically)
{
return GetUSoundWaveFromAudioFile_Internal(filePath, Format, status, DefineFormatAutomatically);
}
/**
* Destroy USoundWave static object
*
* @return Returns true - success, false - failure.
*/
bool URuntimeAudioImporterBPLibrary::DestroySoundWave(USoundWave* ReadySoundWave) {
if (IsValid(ReadySoundWave) && ReadySoundWave->IsValidLowLevel()) {
/* Free memory */
ReadySoundWave->InvalidateCompressedData();
FMemory::Free(ReadySoundWave->RawPCMData);
ReadySoundWave->RawData.RemoveBulkData();
ReadySoundWave->RemoveAudioResource();
/* Free memory */
/* Destroying USoundWave object as UObject */
ReadySoundWave->ConditionalBeginDestroy();
ReadySoundWave = NULL;
/* Destroying USoundWave object as UObject */
return true;
}
else {
return false;
}
}
/* Get USoundWave static object reference */
class USoundWave* URuntimeAudioImporterBPLibrary::GetSoundWaveObject(const uint8* WaveData, int32 WaveDataSize, TEnumAsByte < TranscodingStatus >& status) {
USoundWave* sw = NewObject<USoundWave>(USoundWave::StaticClass());
if (!sw) {
status = TranscodingStatus::USoundWaveDeclarationError;
return nullptr;
}
FWaveModInfo WaveInfo;
FString ErrorReason;
if (WaveInfo.ReadWaveInfo(WaveData, WaveDataSize, &ErrorReason))
{
if (*WaveInfo.pBitsPerSample != 16) {
status = TranscodingStatus::UnsupportedBitDepth;
return nullptr;
}
sw->InvalidateCompressedData();
sw->RawData.Lock(LOCK_READ_WRITE);
void* LockedData = sw->RawData.Realloc(WaveDataSize);
FMemory::Memcpy(LockedData, WaveData, WaveDataSize);
sw->RawData.Unlock();
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;
}
else {
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::InvalidBitrate;
}
else {
status = TranscodingStatus::UnrecognizedReadWaveError;
}
return nullptr;
}
sw->RawPCMData = (uint8*)FMemory::Malloc(sw->RawPCMDataSize);
FMemory::Memcpy(sw->RawPCMData, WaveInfo.SampleDataStart, sw->RawPCMDataSize);
if (!sw) {
status = TranscodingStatus::InvalidUSoundWave;
return nullptr;
}
status = TranscodingStatus::SuccessTranscoding;
return sw;
}
#include "ThirdParty/dr_wav.h"
#include "ThirdParty/dr_mp3.h"
#include "ThirdParty/dr_flac.h"
/* Main internal get USoundWave from audio file */
class USoundWave* URuntimeAudioImporterBPLibrary::GetUSoundWaveFromAudioFile_Internal(const FString& filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus >& status, bool DefineFormatAutomatically)
{
if (FPaths::FileExists(filePath) != true) {
status = TranscodingStatus::AudioDoesNotExist;
return nullptr;
}
int16_t* pSampleData;
uint64 framesToWrite;
uint32 channels;
uint32 sampleRate;
if (DefineFormatAutomatically) {
Format = GetAudioFormat(filePath);
if (Format == AudioFormat::INVALID) {
status = TranscodingStatus::InvalidAudioFormat;
return nullptr;
}
}
if (TranscodeAudioToWaveData(TCHAR_TO_ANSI(*filePath), Format, status, framesToWrite, pSampleData, channels, sampleRate) == false) {
return nullptr;
}
drwav wavEncode;
drwav_data_format format;
format.container = drwav_container_riff;
format.format = DR_WAVE_FORMAT_PCM;
format.channels = channels;
format.sampleRate = sampleRate;
format.bitsPerSample = 16; // Unreal Engine 4 supports only 16 bit rate
void* pWavFileData;
size_t wavFileSize;
drwav_init_memory_write(&wavEncode, &pWavFileData, &wavFileSize, &format, NULL);
drwav_write_pcm_frames(&wavEncode, framesToWrite, pSampleData);
drwav_uninit(&wavEncode);
USoundWave* ReadyUSoundWave = GetSoundWaveObject((const uint8*)pWavFileData, wavFileSize, status); // A ready USoundWave static object
drwav_free(pSampleData, NULL);
drwav_free(pWavFileData, NULL);
return ReadyUSoundWave;
}
/* Automatically get audio format. Internal use only recommended */
TEnumAsByte<AudioFormat> URuntimeAudioImporterBPLibrary::GetAudioFormat(const FString& filePath) {
if (FPaths::GetExtension(filePath, false).Equals(TEXT("mp3"), ESearchCase::IgnoreCase)) {
return AudioFormat::MP3;
}
else if (FPaths::GetExtension(filePath, false).Equals(TEXT("wav"), ESearchCase::IgnoreCase)) {
return AudioFormat::WAV;
}
else if (FPaths::GetExtension(filePath, false).Equals(TEXT("flac"), ESearchCase::IgnoreCase)) {
return AudioFormat::FLAC;
}
else {
return AudioFormat::INVALID;
}
}
/* Transcode Audio to Wave Data */
bool URuntimeAudioImporterBPLibrary::TranscodeAudioToWaveData(const char* filePath, TEnumAsByte<AudioFormat> Format, TEnumAsByte<TranscodingStatus>& status, uint64& framesToWrite, int16_t*& pSampleData, uint32& channels, uint32& sampleRate) {
switch (Format)
{
case AudioFormat::MP3:
{
drmp3 mp3;
if (!drmp3_init_file(&mp3, filePath, NULL)) {
status = TranscodingStatus::FailedToOpenStream;
return false;
}
else {
pSampleData = (int16_t*)malloc((size_t)drmp3_get_pcm_frame_count(&mp3) * mp3.channels * sizeof(int16_t));
framesToWrite = drmp3_read_pcm_frames_s16(&mp3, drmp3_get_pcm_frame_count(&mp3), pSampleData);
channels = mp3.channels;
sampleRate = mp3.sampleRate;
drmp3_uninit(&mp3);
return true;
}
break;
}
case AudioFormat::WAV:
{
drwav wav;
if (!drwav_init_file(&wav, filePath, NULL)) {
status = TranscodingStatus::FailedToOpenStream;
return false;
}
else {
pSampleData = (int16_t*)malloc((size_t)wav.totalPCMFrameCount * wav.channels * sizeof(int16_t));
framesToWrite = drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, pSampleData);
channels = wav.channels;
sampleRate = wav.sampleRate;
drwav_uninit(&wav);
return true;
}
break;
}
case AudioFormat::FLAC:
{
drflac* pFlac = drflac_open_file(filePath, NULL);
if (pFlac == NULL) {
status = TranscodingStatus::FailedToOpenStream;
return false;
}
else {
pSampleData = (int16_t*)malloc((size_t)pFlac->totalPCMFrameCount * pFlac->channels * sizeof(int16_t));
framesToWrite = drflac_read_pcm_frames_s16(pFlac, pFlac->totalPCMFrameCount, pSampleData);
channels = pFlac->channels;
sampleRate = pFlac->sampleRate;
drflac_close(pFlac);
return true;
}
break;
}
default:
{
status = TranscodingStatus::InvalidAudioFormat;
return false;
break;
}
}
}
@@ -0,0 +1,405 @@
// Respirant 2020.
#include "RuntimeAudioImporterLibrary.h"
#include "RuntimeAudioImporter.h"
URuntimeAudioImporterLibrary::URuntimeAudioImporterLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
URuntimeAudioImporterLibrary::~URuntimeAudioImporterLibrary()
{
}
URuntimeAudioImporterLibrary* URuntimeAudioImporterLibrary::CreateRuntimeAudioImporter()
{
URuntimeAudioImporterLibrary* Importer = NewObject<URuntimeAudioImporterLibrary>();
return Importer;
}
URuntimeAudioImporterLibrary* URuntimeAudioImporterLibrary::ImportAudioFromFile(const FString& filePath, TEnumAsByte < AudioFormat > Format, bool DefineFormatAutomatically)
{
AddToRoot();
AsyncTask(ENamedThreads::AnyThread, [=]() {
GetUSoundWaveFromAudioFile_Internal(filePath, Format, DefineFormatAutomatically);
});
return this;
}
bool URuntimeAudioImporterLibrary::DestroySoundWave(USoundWave* ReadySoundWave) {
if (ReadySoundWave->IsValidLowLevel()) {
//Begin USoundWave destroy (block changing some data)
ReadySoundWave->ConditionalBeginDestroy();
ReadySoundWave->BeginDestroy();
//Delete all audio data from physical memory
ReadySoundWave->RawData.RemoveBulkData();
ReadySoundWave->RemoveAudioResource();
ReadySoundWave->InvalidateCompressedData(true);
//Finish USoundWave destroy (complete object deletion as UObject)
if (ReadySoundWave->IsReadyForFinishDestroy()) {
ReadySoundWave->ConditionalFinishDestroy();
ReadySoundWave->FinishDestroy();
return true;
}
else {
return false;
}
}
else {
return false;
}
}
/**
* 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);
}
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);
}
else {
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(this, TCHAR_TO_ANSI(*filePath), Format, Status, framesToWrite, pSampleData, channels, sampleRate) == false) {
FMemory::Free(pSampleData);
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, Status);
}
else {
void* WaveData;
size_t WaveDataSize;
if (TranscodePCMToWAVData(this, *&WaveData, *&WaveDataSize, framesToWrite, *&pSampleData, channels, sampleRate) == false) {
FMemory::Free(pSampleData);
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, Status);
}
else {
USoundWave* ReadyUSoundWave = GetSoundWaveObject(this, (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 this;
}
class USoundWave* URuntimeAudioImporterLibrary::GetSoundWaveObject(URuntimeAudioImporterLibrary* RuntimeAudioImporterObjRef, const uint8* WaveData, int32 WaveDataSize, TEnumAsByte < TranscodingStatus >& Status)
{
USoundWave* sw = NewObject<USoundWave>(USoundWave::StaticClass());
if (!sw) {
Status = TranscodingStatus::USoundWaveDeclarationError;
return nullptr;
}
FWaveModInfo WaveInfo;
FString ErrorReason;
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(65);
if (WaveInfo.ReadWaveInfo(WaveData, WaveDataSize, &ErrorReason))
{
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(75);
if (*WaveInfo.pBitsPerSample != 16) {
Status = TranscodingStatus::UnsupportedBitDepth;
return nullptr;
}
sw->InvalidateCompressedData();
sw->RawData.Lock(LOCK_READ_WRITE);
void* LockedData = sw->RawData.Realloc(WaveDataSize);
FMemory::Memcpy(LockedData, WaveData, WaveDataSize);
sw->RawData.Unlock();
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(80);
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)
RuntimeAudioImporterObjRef->OnProgress_Internal(85);
}
else {
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::InvalidBitrate;
}
else {
Status = TranscodingStatus::UnrecognizedReadWaveError;
}
return nullptr;
}
sw->RawPCMData = (uint8*)FMemory::Malloc(sw->RawPCMDataSize);
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(90);
FMemory::Memcpy(sw->RawPCMData, WaveInfo.SampleDataStart, sw->RawPCMDataSize);
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(95);
if (!sw) {
Status = TranscodingStatus::InvalidUSoundWave;
return nullptr;
}
Status = TranscodingStatus::SuccessTranscoding;
return sw;
}
bool URuntimeAudioImporterLibrary::TranscodeAudioFileToPCMData(URuntimeAudioImporterLibrary* RuntimeAudioImporterObjRef, const char* filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus >& Status, uint64& framesToWrite, int16_t*& pSampleData, uint32& channels, uint32& sampleRate)
{
RuntimeAudioImporterObjRef->OnProgress_Internal(5);
switch (Format)
{
case AudioFormat::MP3:
{
drmp3_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = NULL;
allocationCallbacksDecoding.onMalloc = unreal_malloc;
allocationCallbacksDecoding.onRealloc = unreal_realloc;
allocationCallbacksDecoding.onFree = unreal_free;
drmp3 mp3;
if (!drmp3_init_file(&mp3, filePath, &allocationCallbacksDecoding)) {
Status = TranscodingStatus::FailedToOpenStream;
return false;
}
else {
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->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)
RuntimeAudioImporterObjRef->OnProgress_Internal(15);
framesToWrite = drmp3_read_pcm_frames_s16(&mp3, drmp3_get_pcm_frame_count(&mp3), pSampleData);
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(35);
channels = mp3.channels;
sampleRate = mp3.sampleRate;
drmp3_uninit(&mp3);
return true;
}
break;
}
case AudioFormat::WAV:
{
drwav_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = NULL;
allocationCallbacksDecoding.onMalloc = unreal_malloc;
allocationCallbacksDecoding.onRealloc = unreal_realloc;
allocationCallbacksDecoding.onFree = unreal_free;
drwav wav;
if (!drwav_init_file(&wav, filePath, &allocationCallbacksDecoding)) {
Status = TranscodingStatus::FailedToOpenStream;
return false;
}
else {
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(10);
pSampleData = (int16_t*)FMemory::Malloc((size_t)wav.totalPCMFrameCount * wav.channels * sizeof(int16_t));
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(15);
framesToWrite = drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, pSampleData);
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(35);
channels = wav.channels;
sampleRate = wav.sampleRate;
drwav_uninit(&wav);
return true;
}
break;
}
case AudioFormat::FLAC:
{
drflac_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = NULL;
allocationCallbacksDecoding.onMalloc = unreal_malloc;
allocationCallbacksDecoding.onRealloc = unreal_realloc;
allocationCallbacksDecoding.onFree = unreal_free;
drflac* pFlac = drflac_open_file(filePath, &allocationCallbacksDecoding);
if (pFlac == NULL) {
Status = TranscodingStatus::FailedToOpenStream;
return false;
}
else {
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(10);
pSampleData = (int16_t*)FMemory::Malloc((size_t)pFlac->totalPCMFrameCount * pFlac->channels * sizeof(int16_t));
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(15);
framesToWrite = drflac_read_pcm_frames_s16(pFlac, pFlac->totalPCMFrameCount, pSampleData);
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(35);
channels = pFlac->channels;
sampleRate = pFlac->sampleRate;
drflac_close(pFlac);
return true;
}
break;
}
default:
{
Status = TranscodingStatus::InvalidAudioFormat;
return false;
break;
}
}
}
bool URuntimeAudioImporterLibrary::TranscodePCMToWAVData(URuntimeAudioImporterLibrary* RuntimeAudioImporterObjRef, void*& WaveData, size_t& WaveDataSize, uint64 framesToWrite, int16_t*& pSampleData, uint32 channels, uint32 sampleRate) {
drwav wavEncode;
drwav_data_format format;
format.container = drwav_container_riff;
format.format = DR_WAVE_FORMAT_PCM;
format.channels = channels;
format.sampleRate = 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)) {
return false; //error
}
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(40);
drwav_write_pcm_frames(&wavEncode, framesToWrite, pSampleData);
// Callback Dispatcher OnProgress (not completely accurate implementation)
RuntimeAudioImporterObjRef->OnProgress_Internal(60);
drwav_uninit(&wavEncode);
return true;
}
TEnumAsByte<AudioFormat> URuntimeAudioImporterLibrary::GetAudioFormat(const FString& filePath)
{
if (FPaths::GetExtension(filePath, false).Equals(TEXT("mp3"), ESearchCase::IgnoreCase)) {
return AudioFormat::MP3;
}
else if (FPaths::GetExtension(filePath, false).Equals(TEXT("wav"), ESearchCase::IgnoreCase)) {
return AudioFormat::WAV;
}
else if (FPaths::GetExtension(filePath, false).Equals(TEXT("flac"), ESearchCase::IgnoreCase)) {
return AudioFormat::FLAC;
}
else {
return AudioFormat::INVALID;
}
}
void URuntimeAudioImporterLibrary::OnProgress_Internal(int32 Percentage)
{
AsyncTask(ENamedThreads::GameThread, [=]() {
OnProgress.Broadcast(Percentage);
});
}
void URuntimeAudioImporterLibrary::OnResult_Internal(USoundWave* ReadySoundWave, TEnumAsByte < TranscodingStatus > Status)
{
AsyncTask(ENamedThreads::GameThread, [=]() {
RemoveFromRoot();
OnResult.Broadcast(ReadySoundWave, Status);
});
}
@@ -1,79 +0,0 @@
// Respirant 2020.
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Sound/SoundWave.h"
#include "Developer/TargetPlatform/Public/Interfaces/IAudioFormat.h"
#include "RuntimeAudioImporterBPLibrary.generated.h"
UENUM(BlueprintType)
enum TranscodingStatus
{
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")
};
UENUM(BlueprintType)
enum AudioFormat
{
MP3 UMETA(DisplayName = "mp3"),
WAV UMETA(DisplayName = "wav"),
FLAC UMETA(DisplayName = "flac"),
INVALID UMETA(DisplayName = "invalid (not defined format, internal use only)", Hidden)
};
UCLASS()
class RUNTIMEAUDIOIMPORTER_API URuntimeAudioImporterBPLibrary : public UBlueprintFunctionLibrary
{
GENERATED_UCLASS_BODY()
public:
/**
* Transcode audio file to USoundWave static object
*
* @param filePath Path to the audio file to import
* @param Format Audio file format (extension)
* @param status Final import status (TranscodingStatus)
* @param DefineFormatAutomatically Whether to define format (extension) automatically or not
* @return Returns USoundWave Static Object.
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, MP3, FLAC, WAV", ToolTip = "Transcode audio file to USoundWave static object"), Category = "RuntimeAudioImporter")
static class USoundWave* GetSoundWaveFromAudioFile(const FString & filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus > & status, bool DefineFormatAutomatically);
/**
* Destroy USoundWave static object
*
* @return Returns true - success, false - failure.
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, MP3, FLAC, WAV, Destroy", ToolTip = "After the Soundwave is no longer needed, you need to call this function to free memory"), Category = "RuntimeAudioImporter")
static bool DestroySoundWave(USoundWave * ReadySoundWave);
private:
/* Main internal get USoundWave from audio file */
static class USoundWave* GetUSoundWaveFromAudioFile_Internal(const FString & filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus > & status, bool DefineFormatAutomatically);
/* Get USoundWave static object reference */
static class USoundWave* GetSoundWaveObject(const uint8 * WaveData, int32 WaveDataSize, TEnumAsByte < TranscodingStatus > & status);
/* Transcode Audio to Wave Data */
static bool TranscodeAudioToWaveData(const char* filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus > & status, uint64 & framesToWrite, int16_t * &pSampleData, uint32 & channels, uint32 & sampleRate);
/* Automatically get audio format. Internal use only recommended */
static TEnumAsByte<AudioFormat> GetAudioFormat(const FString & filePath);
};
@@ -0,0 +1,175 @@
// Respirant 2020.
#pragma once
#include "Sound/SoundWave.h"
//#include "Developer/TargetPlatform/Public/Interfaces/IAudioFormat.h"
#include "Async/Async.h"
#include "RuntimeAudioImporterLibrary.generated.h"
/**
* Possible audio importing results
*/
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
enum TranscodingStatus
{
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")
};
/**
* Possible audio formats
*/
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
enum AudioFormat
{
MP3 UMETA(DisplayName = "mp3"),
WAV UMETA(DisplayName = "wav"),
FLAC UMETA(DisplayName = "flac"),
INVALID UMETA(DisplayName = "invalid (not defined format, internal use only)", Hidden)
};
/**
* 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
*/
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
*/
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnResult, const USoundWave*, ReadySoundWave, const TEnumAsByte < TranscodingStatus >, Status);
UCLASS(BlueprintType, Category = "RuntimeFilesDownloader")
class RUNTIMEAUDIOIMPORTER_API URuntimeAudioImporterLibrary : public UObject
{
GENERATED_UCLASS_BODY()
public:
/**
* 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).
*/
UPROPERTY(BlueprintAssignable, Category = "RuntimeAudioImporter")
FOnResult OnResult;
URuntimeAudioImporterLibrary();
~URuntimeAudioImporterLibrary();
/**
* 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.
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Create, Audio, Runtime, MP3, FLAC, WAV"), Category = "RuntimeAudioImporter")
static URuntimeAudioImporterLibrary * CreateRuntimeAudioImporter();
/**
* Transcode audio file to USoundWave static 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.
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV"), Category = "RuntimeAudioImporter")
URuntimeAudioImporterLibrary * ImportAudioFromFile(const FString & filePath, TEnumAsByte < AudioFormat > Format, bool DefineFormatAutomatically);
/**
* Destroy USoundWave static object. After the Soundwave is no longer needed, you need to call this function to free memory
*
* @param ReadySoundWave SoundWave Object need to be destroyed
* @return Whether the descruction of USoundWave was successfull or not
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "MP3, FLAC, WAV, Destroy"), Category = "RuntimeAudioImporter")
static bool DestroySoundWave(USoundWave * ReadySoundWave);
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
*/
URuntimeAudioImporterLibrary * GetUSoundWaveFromAudioFile_Internal(const FString & filePath, TEnumAsByte < AudioFormat > Format, bool DefineFormatAutomatically);
/**
* Get USoundWave static object reference from 16-bit WAV data
*
* @param RuntimeAudioImporterObjRef Reference to the created RuntimeAudioImporter object
* @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
*/
static class USoundWave* GetSoundWaveObject(URuntimeAudioImporterLibrary * RuntimeAudioImporterObjRef, const uint8 * WaveData, int32 WaveDataSize, TEnumAsByte < TranscodingStatus > & Status);
/**
* Transcode Audio from File to PCM Data
*
* @param RuntimeAudioImporterObjRef Reference to the created RuntimeAudioImporter object
* @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
*/
static bool TranscodeAudioFileToPCMData(URuntimeAudioImporterLibrary * RuntimeAudioImporterObjRef, const char* filePath, TEnumAsByte < AudioFormat > Format, TEnumAsByte < TranscodingStatus > & Status, uint64 & framesToWrite, int16_t * &pSampleData, uint32 & channels, uint32 & sampleRate);
/**
* Transcode Audio from PCM to 16-bit WAV data
*
* @param RuntimeAudioImporterObjRef Reference to the created RuntimeAudioImporter object
* @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
*/
static bool TranscodePCMToWAVData(URuntimeAudioImporterLibrary* RuntimeAudioImporterObjRef, void*& WaveData, size_t & WaveDataSize, uint64 framesToWrite, int16_t * &pSampleData, uint32 channels, uint32 sampleRate);
/**
* @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
*/
static TEnumAsByte<AudioFormat> GetAudioFormat(const FString & filePath);
/**
* Audio transcoding progress callback
* @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
*/
void OnProgress_Internal(int32 Percentage);
/**
* Audio transcoding finished callback
* @param ReadySoundWave A ready USoundWave object
* @param Status Reference to TranscodingStatus Enumerator
*/
void OnResult_Internal(USoundWave * ReadySoundWave, TEnumAsByte < TranscodingStatus > Status);
};
@@ -1,15 +1,13 @@
// Respirant 2020.
using System;
using System.IO;
using UnrealBuildTool;
public class RuntimeAudioImporter : ModuleRules
{
public RuntimeAudioImporter(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
public RuntimeAudioImporter(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDefinitions.AddRange(
new string[] {
@@ -19,35 +17,33 @@ public class RuntimeAudioImporter : ModuleRules
}
);
//PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "ThirdParty"));
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
new string[]
{
"Core",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
);
}
}