mirror of
https://github.com/storytold/RuntimeAudioImporter.git
synced 2026-10-22 08:15:41 +00:00
big update: callback dispatchers, memory leak fixes and more
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -4,37 +4,21 @@
|
|||||||
"VersionName": "1.0",
|
"VersionName": "1.0",
|
||||||
"FriendlyName": "RuntimeAudioImporter",
|
"FriendlyName": "RuntimeAudioImporter",
|
||||||
"Description": "Runtime Audio Importer plugin for Unreal Engine 4. Supports mp3, wav and flac formats.",
|
"Description": "Runtime Audio Importer plugin for Unreal Engine 4. Supports mp3, wav and flac formats.",
|
||||||
"Category": "Code Plugins",
|
"Category": "Audio",
|
||||||
"CreatedBy": "Respirant",
|
"CreatedBy": "Respirant",
|
||||||
"CreatedByURL": "https://github.com/Respirant/RuntimeAudioImporter",
|
"CreatedByURL": "https://github.com/Respirant/RuntimeAudioImporter",
|
||||||
"DocsURL": "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",
|
"SupportURL": "https://telegram.me/Respirant",
|
||||||
"CanContainContent": false,
|
"CanContainContent": false,
|
||||||
"IsBetaVersion": false,
|
"IsBetaVersion": false,
|
||||||
"IsExperimentalVersion": false,
|
"IsExperimentalVersion": false,
|
||||||
"Installed": true,
|
"Installed": true,
|
||||||
"EngineVersion": "4.26.0",
|
|
||||||
"Modules": [
|
"Modules": [
|
||||||
{
|
{
|
||||||
"Name": "RuntimeAudioImporter",
|
"Name": "RuntimeAudioImporter",
|
||||||
"Type": "Runtime",
|
"Type": "Runtime",
|
||||||
"LoadingPhase": "Default",
|
"LoadingPhase": "Default"
|
||||||
"WhitelistPlatforms": [
|
|
||||||
"Win64",
|
|
||||||
"Win32",
|
|
||||||
"Mac",
|
|
||||||
"Android",
|
|
||||||
"IOS",
|
|
||||||
"Linux"
|
|
||||||
],
|
|
||||||
"BlacklistPlatforms": [
|
|
||||||
"XboxOne",
|
|
||||||
"PS4",
|
|
||||||
"HTML5",
|
|
||||||
"TVOS",
|
|
||||||
"Switch"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
-248
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+405
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
-79
@@ -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);
|
|
||||||
};
|
|
||||||
+175
@@ -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.
|
// Respirant 2020.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using UnrealBuildTool;
|
using UnrealBuildTool;
|
||||||
|
|
||||||
public class RuntimeAudioImporter : ModuleRules
|
public class RuntimeAudioImporter : ModuleRules
|
||||||
{
|
{
|
||||||
public RuntimeAudioImporter(ReadOnlyTargetRules Target) : base(Target)
|
public RuntimeAudioImporter(ReadOnlyTargetRules Target) : base(Target)
|
||||||
{
|
{
|
||||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
|
||||||
PublicDefinitions.AddRange(
|
PublicDefinitions.AddRange(
|
||||||
new string[] {
|
new string[] {
|
||||||
@@ -19,35 +17,33 @@ public class RuntimeAudioImporter : ModuleRules
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
//PrivateIncludePaths.Add(Path.Combine(ModuleDirectory, "ThirdParty"));
|
|
||||||
|
|
||||||
|
|
||||||
PublicDependencyModuleNames.AddRange(
|
PublicDependencyModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"Core",
|
"Core",
|
||||||
// ... add other public dependencies that you statically link with here ...
|
// ... add other public dependencies that you statically link with here ...
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
PrivateDependencyModuleNames.AddRange(
|
PrivateDependencyModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"CoreUObject",
|
"CoreUObject",
|
||||||
"Engine",
|
"Engine",
|
||||||
"Slate",
|
"Slate",
|
||||||
"SlateCore",
|
"SlateCore",
|
||||||
// ... add private dependencies that you statically link with here ...
|
// ... add private dependencies that you statically link with here ...
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
DynamicallyLoadedModuleNames.AddRange(
|
DynamicallyLoadedModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
// ... add any modules that your module loads dynamically here ...
|
// ... add any modules that your module loads dynamically here ...
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user