检查卡顿问题
This commit is contained in:
parent
7112e49fbe
commit
6b02499289
60
Cut5.sln
60
Cut5.sln
@ -7,49 +7,67 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine", "Engine", "{233774
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Games", "Games", "{DE1F8B53-6C02-3C13-9101-A7C8D96F3FF6}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UE5", "Intermediate\ProjectFiles\UE5.vcxproj", "{6EE39883-7339-3FB6-AD82-931FB137D37F}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cut5", "Intermediate\ProjectFiles\Cut5.vcxproj", "{B95E7D0E-DB45-3765-9058-E00EBBC4B157}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Cut5", "Intermediate\ProjectFiles\Cut5.vcxproj", "{AF5A253A-0F37-38CE-8998-45CA936C112B}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UE5", "Intermediate\ProjectFiles\UE5.vcxproj", "{C48D0E9D-C862-3EA3-96A7-752EE9D06362}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Visualizers", "Visualizers", "{1CCEC849-CC72-4C59-8C36-2F7C38706D4C}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
D:\UE\UE_5.2\Engine\Extras\VisualStudioDebugging\Unreal.natvis = D:\UE\UE_5.2\Engine\Extras\VisualStudioDebugging\Unreal.natvis
|
||||
..\..\Software\UE_5.2\Engine\Extras\VisualStudioDebugging\Unreal.natvis = ..\..\Software\UE_5.2\Engine\Extras\VisualStudioDebugging\Unreal.natvis
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
DebugGame Editor|Android = DebugGame Editor|Android
|
||||
DebugGame Editor|Win64 = DebugGame Editor|Win64
|
||||
DebugGame|Android = DebugGame|Android
|
||||
DebugGame|Win64 = DebugGame|Win64
|
||||
Development Editor|Android = Development Editor|Android
|
||||
Development Editor|Win64 = Development Editor|Win64
|
||||
Development|Android = Development|Android
|
||||
Development|Win64 = Development|Win64
|
||||
Shipping|Android = Shipping|Android
|
||||
Shipping|Win64 = Shipping|Win64
|
||||
EndGlobalSection
|
||||
# UnrealVS Section
|
||||
GlobalSection(ddbf523f-7eb6-4887-bd51-85a714ff87eb) = preSolution
|
||||
AvailablePlatforms=Win64
|
||||
AvailablePlatforms=Win64;Android
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6EE39883-7339-3FB6-AD82-931FB137D37F}.DebugGame Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{6EE39883-7339-3FB6-AD82-931FB137D37F}.DebugGame|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{6EE39883-7339-3FB6-AD82-931FB137D37F}.Development Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{6EE39883-7339-3FB6-AD82-931FB137D37F}.Development|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{6EE39883-7339-3FB6-AD82-931FB137D37F}.Shipping|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.DebugGame Editor|Win64.ActiveCfg = DebugGame_Editor|x64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.DebugGame Editor|Win64.Build.0 = DebugGame_Editor|x64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.DebugGame|Win64.ActiveCfg = DebugGame|x64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.DebugGame|Win64.Build.0 = DebugGame|x64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Development Editor|Win64.ActiveCfg = Development_Editor|x64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Development Editor|Win64.Build.0 = Development_Editor|x64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Development|Win64.ActiveCfg = Development|x64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Development|Win64.Build.0 = Development|x64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Shipping|Win64.ActiveCfg = Shipping|x64
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B}.Shipping|Win64.Build.0 = Shipping|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame Editor|Android.ActiveCfg = Invalid|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame Editor|Win64.ActiveCfg = DebugGame_Editor|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame Editor|Win64.Build.0 = DebugGame_Editor|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame|Android.ActiveCfg = Android_DebugGame|Win64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame|Android.Build.0 = Android_DebugGame|Win64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame|Win64.ActiveCfg = DebugGame|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.DebugGame|Win64.Build.0 = DebugGame|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development Editor|Android.ActiveCfg = Invalid|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development Editor|Win64.ActiveCfg = Development_Editor|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development Editor|Win64.Build.0 = Development_Editor|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development|Android.ActiveCfg = Android_Development|Win64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development|Android.Build.0 = Android_Development|Win64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development|Win64.ActiveCfg = Development|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Development|Win64.Build.0 = Development|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Shipping|Android.ActiveCfg = Android_Shipping|Win64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Shipping|Android.Build.0 = Android_Shipping|Win64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Shipping|Win64.ActiveCfg = Shipping|x64
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157}.Shipping|Win64.Build.0 = Shipping|x64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.DebugGame Editor|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.DebugGame Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.DebugGame|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.DebugGame|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Development Editor|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Development Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Development|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Development|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Shipping|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362}.Shipping|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{6EE39883-7339-3FB6-AD82-931FB137D37F} = {233774A8-CC9D-3FA9-86D1-90573E92B704}
|
||||
{AF5A253A-0F37-38CE-8998-45CA936C112B} = {DE1F8B53-6C02-3C13-9101-A7C8D96F3FF6}
|
||||
{C48D0E9D-C862-3EA3-96A7-752EE9D06362} = {233774A8-CC9D-3FA9-86D1-90573E92B704}
|
||||
{B95E7D0E-DB45-3765-9058-E00EBBC4B157} = {DE1F8B53-6C02-3C13-9101-A7C8D96F3FF6}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -24,6 +24,15 @@
|
||||
{
|
||||
"Name": "OpenCV",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "SoundUtilities",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "RuntimeAudioImporter",
|
||||
"Enabled": true,
|
||||
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036"
|
||||
}
|
||||
]
|
||||
}
|
BIN
Plugins/RuntimeAudioImporter/Resources/Icon128.png
Normal file
BIN
Plugins/RuntimeAudioImporter/Resources/Icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
28
Plugins/RuntimeAudioImporter/RuntimeAudioImporter.uplugin
Normal file
28
Plugins/RuntimeAudioImporter/RuntimeAudioImporter.uplugin
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0",
|
||||
"FriendlyName": "Runtime Audio Importer",
|
||||
"Description": "Runtime Audio Importer is an open-source plugin for importing audio of various formats at runtime, including MP3, WAV, FLAC, OGG Vorbis, BINK, and RAW (int8, uint8, int16, uint16, int32, uint32, float32).",
|
||||
"Category": "Audio",
|
||||
"CreatedBy": "Georgy Treshchev",
|
||||
"CreatedByURL": "https://github.com/gtreshchev",
|
||||
"DocsURL": "https://github.com/gtreshchev/RuntimeAudioImporter/wiki",
|
||||
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036",
|
||||
"SupportURL": "https://georgy.dev/",
|
||||
"EngineVersion": "5.2.0",
|
||||
"CanContainContent": true,
|
||||
"Installed": true,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "RuntimeAudioImporter",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
},
|
||||
{
|
||||
"Name": "RuntimeAudioImporterEditor",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "Codecs/BINK_RuntimeCodec.h"
|
||||
#include "RuntimeAudioImporterTypes.h"
|
||||
#include "Codecs/RAW_RuntimeCodec.h"
|
||||
#include "HAL/PlatformProperties.h"
|
||||
#include "HAL/UnrealMemory.h"
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_BINK_DECODE_SUPPORT
|
||||
#include "BinkAudioInfo.h"
|
||||
#include "Interfaces/IAudioFormat.h"
|
||||
#endif
|
||||
|
||||
#define INCLUDE_BINK
|
||||
#include "CodecIncludes.h"
|
||||
#undef INCLUDE_BINK
|
||||
|
||||
|
||||
/**
|
||||
* Taken from AudioFormatBink.cpp
|
||||
*/
|
||||
namespace
|
||||
{
|
||||
uint8 GetCompressionLevelFromQualityIndex(int32 InQualityIndex)
|
||||
{
|
||||
// Bink goes from 0 (best) to 9 (worst), but is basically unusable below 4
|
||||
static constexpr float BinkLowest = 4;
|
||||
static constexpr float BinkHighest = 0;
|
||||
|
||||
// Map Quality 1 (lowest) to 40 (highest)
|
||||
static constexpr float QualityLowest = 1;
|
||||
static constexpr float QualityHighest = 40;
|
||||
|
||||
return FMath::GetMappedRangeValueClamped(FVector2D(QualityLowest, QualityHighest), FVector2D(BinkLowest, BinkHighest), InQualityIndex);
|
||||
}
|
||||
|
||||
void* BinkAlloc(size_t Bytes)
|
||||
{
|
||||
return FMemory::Malloc(Bytes, 16);
|
||||
}
|
||||
|
||||
void BinkFree(void* Ptr)
|
||||
{
|
||||
FMemory::Free(Ptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool FBINK_RuntimeCodec::CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData)
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_BINK_DECODE_SUPPORT
|
||||
FBinkAudioInfo AudioInfo;
|
||||
FSoundQualityInfo SoundQualityInfo;
|
||||
|
||||
if (!AudioInfo.ReadCompressedInfo(AudioData.GetView().GetData(), AudioData.GetView().Num(), &SoundQualityInfo) || SoundQualityInfo.SampleDataSize == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Your platform (%hs) does not support BINK decoding"), FPlatformProperties::IniPlatformName());
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FBINK_RuntimeCodec::GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Retrieving header information for the BINK audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to retrieve audio header information in the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_BINK_DECODE_SUPPORT
|
||||
FBinkAudioInfo AudioInfo;
|
||||
FSoundQualityInfo SoundQualityInfo;
|
||||
|
||||
if (!AudioInfo.ReadCompressedInfo(EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), &SoundQualityInfo) || SoundQualityInfo.SampleDataSize == 0)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to read BINK compressed info"));
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
HeaderInfo.Duration = SoundQualityInfo.Duration;
|
||||
HeaderInfo.SampleRate = SoundQualityInfo.SampleRate;
|
||||
HeaderInfo.NumOfChannels = SoundQualityInfo.NumChannels;
|
||||
HeaderInfo.PCMDataSize = SoundQualityInfo.SampleDataSize / sizeof(int16);
|
||||
HeaderInfo.AudioFormat = GetAudioFormat();
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully retrieved header information for FLAC audio format.\nHeader info: %s"), *HeaderInfo.ToString());
|
||||
return true;
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Your platform (%hs) does not support BINK decoding"), FPlatformProperties::IniPlatformName());
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FBINK_RuntimeCodec::Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Encoding uncompressed audio data to BINK audio format.\nDecoded audio info: %s.\nQuality: %d"), *DecodedData.ToString(), Quality);
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_BINK_ENCODE_SUPPORT
|
||||
const uint8 CompressionLevel = GetCompressionLevelFromQualityIndex(Quality);
|
||||
|
||||
int16* TempInt16Buffer;
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<float, int16>(DecodedData.PCMInfo.PCMData.GetView().GetData(), DecodedData.PCMInfo.PCMData.GetView().Num(), TempInt16Buffer);
|
||||
|
||||
void* CompressedData = nullptr;
|
||||
uint32_t CompressedDataLen = 0;
|
||||
UECompressBinkAudio(static_cast<void*>(TempInt16Buffer), DecodedData.PCMInfo.PCMData.GetView().Num() * sizeof(int16), DecodedData.SoundWaveBasicInfo.SampleRate, DecodedData.SoundWaveBasicInfo.NumOfChannels, CompressionLevel, 1, BinkAlloc, BinkFree, &CompressedData, &CompressedDataLen);
|
||||
|
||||
if (CompressedDataLen <= 0)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to decode BINK audio data: the uncompressed data is empty"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Populating the encoded audio data
|
||||
{
|
||||
EncodedData.AudioData = FRuntimeBulkDataBuffer<uint8>(static_cast<uint8*>(CompressedData), CompressedDataLen);
|
||||
EncodedData.AudioFormat = ERuntimeAudioFormat::Bink;
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully encoded uncompressed audio data to BINK audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
return true;
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Your platform (%hs) does not support BINK encoding"), FPlatformProperties::IniPlatformName());
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FBINK_RuntimeCodec::Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Decoding BINK audio data to uncompressed audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to decode audio data using the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_BINK_DECODE_SUPPORT
|
||||
FBinkAudioInfo AudioInfo;
|
||||
FSoundQualityInfo SoundQualityInfo;
|
||||
|
||||
// Parse the audio header for the relevant information
|
||||
if (!AudioInfo.ReadCompressedInfo(EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), &SoundQualityInfo))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to read BINK compressed info"));
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray64<uint8> PCMData;
|
||||
|
||||
// Decompress all the sample data
|
||||
PCMData.Empty(SoundQualityInfo.SampleDataSize);
|
||||
PCMData.AddZeroed(SoundQualityInfo.SampleDataSize);
|
||||
AudioInfo.ExpandFile(PCMData.GetData(), &SoundQualityInfo);
|
||||
|
||||
// Getting the number of frames
|
||||
DecodedData.PCMInfo.PCMNumOfFrames = PCMData.Num() / SoundQualityInfo.NumChannels / sizeof(int16);
|
||||
|
||||
const int64 NumOfSamples = PCMData.Num() / sizeof(int16);
|
||||
|
||||
// Transcoding int16 to float format
|
||||
{
|
||||
float* TempFloatBuffer;
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<int16, float>(reinterpret_cast<int16*>(PCMData.GetData()), NumOfSamples, TempFloatBuffer);
|
||||
DecodedData.PCMInfo.PCMData = FRuntimeBulkDataBuffer<float>(TempFloatBuffer, NumOfSamples);
|
||||
}
|
||||
|
||||
// Getting basic audio information
|
||||
{
|
||||
DecodedData.SoundWaveBasicInfo.Duration = SoundQualityInfo.Duration;
|
||||
DecodedData.SoundWaveBasicInfo.NumOfChannels = SoundQualityInfo.NumChannels;
|
||||
DecodedData.SoundWaveBasicInfo.SampleRate = SoundQualityInfo.SampleRate;
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully decoded BINK audio data to uncompressed audio format.\nDecoded audio info: %s"), *DecodedData.ToString());
|
||||
return true;
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Your platform (%hs) does not support BINK decoding"), FPlatformProperties::IniPlatformName());
|
||||
return false;
|
||||
#endif
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
/**
|
||||
* Replacing C dynamic memory management functions
|
||||
* (calloc, malloc, free, realloc, memset, memcpy) with FMemory ones
|
||||
*/
|
||||
#undef calloc
|
||||
#undef malloc
|
||||
#undef free
|
||||
#undef realloc
|
||||
#undef memset
|
||||
#undef memcpy
|
||||
|
||||
#define calloc(Count, Size) [&]() { void* MemPtr = FMemory::Malloc(Count * Size); if (MemPtr) { FMemory::Memset(MemPtr, 0, Count * Size); } return MemPtr; }()
|
||||
#define malloc(Count) FMemory::Malloc(Count)
|
||||
#define free(Original) FMemory::Free(Original)
|
||||
#define realloc(Original, Count) FMemory::Realloc(Original, Count)
|
||||
#define memset(Dest, Char, Count) FMemory::Memset(Dest, Char, Count)
|
||||
#define memcpy(Dest, Src, Count) FMemory::Memcpy(Dest, Src, Count)
|
||||
|
||||
#ifdef INCLUDE_MP3
|
||||
#define DRMP3_API static
|
||||
#define DRMP3_PRIVATE static
|
||||
#include "ThirdParty/dr_mp3.h"
|
||||
#endif
|
||||
|
||||
#ifdef INCLUDE_WAV
|
||||
#define DRWAV_API static
|
||||
#define DRWAV_PRIVATE static
|
||||
#include "ThirdParty/dr_wav.h"
|
||||
#endif
|
||||
|
||||
#ifdef INCLUDE_FLAC
|
||||
#define DRFLAC_API static
|
||||
#define DRFLAC_PRIVATE static
|
||||
#include "ThirdParty/dr_flac.h"
|
||||
#endif
|
||||
|
||||
#ifdef INCLUDE_VORBIS
|
||||
#if PLATFORM_SUPPORTS_VORBIS_CODEC
|
||||
#pragma pack(push, 8)
|
||||
#include "vorbis/vorbisenc.h"
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_BINK_ENCODE_SUPPORT
|
||||
#ifdef INCLUDE_BINK
|
||||
#include "binka_ue_file_header.h"
|
||||
#include "binka_ue_encode.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#undef calloc
|
||||
#undef malloc
|
||||
#undef free
|
||||
#undef realloc
|
||||
#undef memset
|
||||
#undef memcpy
|
@ -0,0 +1,101 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "Codecs/FLAC_RuntimeCodec.h"
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
#include "RuntimeAudioImporterTypes.h"
|
||||
#include "HAL/UnrealMemory.h"
|
||||
|
||||
#define INCLUDE_FLAC
|
||||
#include "CodecIncludes.h"
|
||||
#undef INCLUDE_FLAC
|
||||
|
||||
bool FFLAC_RuntimeCodec::CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData)
|
||||
{
|
||||
drflac* FLAC = drflac_open_memory(AudioData.GetView().GetData(), AudioData.GetView().Num(), nullptr);
|
||||
|
||||
if (!FLAC)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drflac_close(FLAC);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFLAC_RuntimeCodec::GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Retrieving header information for FLAC audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to retrieve audio header information in the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
drflac* FLAC = drflac_open_memory(EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), nullptr);
|
||||
if (!FLAC)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to initialize FLAC Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
HeaderInfo.Duration = static_cast<float>(FLAC->totalPCMFrameCount) / FLAC->sampleRate;
|
||||
HeaderInfo.NumOfChannels = FLAC->channels;
|
||||
HeaderInfo.SampleRate = FLAC->sampleRate;
|
||||
HeaderInfo.PCMDataSize = FLAC->totalPCMFrameCount * FLAC->channels;
|
||||
HeaderInfo.AudioFormat = GetAudioFormat();
|
||||
}
|
||||
|
||||
drflac_close(FLAC);
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully retrieved header information for FLAC audio format.\nHeader info: %s"), *HeaderInfo.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FFLAC_RuntimeCodec::Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality)
|
||||
{
|
||||
ensureMsgf(false, TEXT("FLAC codec does not support encoding at the moment"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FFLAC_RuntimeCodec::Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Decoding FLAC audio data to uncompressed audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to decode audio data using the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
// Initializing FLAC codec
|
||||
drflac* FLAC_Decoder = drflac_open_memory(EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), nullptr);
|
||||
|
||||
if (!FLAC_Decoder)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to initialize FLAC Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocating memory for PCM data
|
||||
float* TempPCMData = static_cast<float*>(FMemory::Malloc(FLAC_Decoder->totalPCMFrameCount * FLAC_Decoder->channels * sizeof(float)));
|
||||
|
||||
if (!TempPCMData)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to allocate memory for FLAC Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filling in PCM data and getting the number of frames
|
||||
DecodedData.PCMInfo.PCMNumOfFrames = drflac_read_pcm_frames_f32(FLAC_Decoder, FLAC_Decoder->totalPCMFrameCount, TempPCMData);
|
||||
|
||||
// Getting PCM data size
|
||||
const int64 TempPCMDataSize = static_cast<int64>(DecodedData.PCMInfo.PCMNumOfFrames * FLAC_Decoder->channels);
|
||||
|
||||
DecodedData.PCMInfo.PCMData = FRuntimeBulkDataBuffer<float>(TempPCMData, TempPCMDataSize);
|
||||
|
||||
// Getting basic audio information
|
||||
{
|
||||
DecodedData.SoundWaveBasicInfo.Duration = static_cast<float>(FLAC_Decoder->totalPCMFrameCount) / FLAC_Decoder->sampleRate;
|
||||
DecodedData.SoundWaveBasicInfo.NumOfChannels = FLAC_Decoder->channels;
|
||||
DecodedData.SoundWaveBasicInfo.SampleRate = FLAC_Decoder->sampleRate;
|
||||
}
|
||||
|
||||
drflac_close(FLAC_Decoder);
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully decoded FLAC audio data to uncompressed audio format.\nDecoded audio info: %s"), *DecodedData.ToString());
|
||||
return true;
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "Codecs/MP3_RuntimeCodec.h"
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
#include "RuntimeAudioImporterTypes.h"
|
||||
#include "HAL/UnrealMemory.h"
|
||||
|
||||
#define INCLUDE_MP3
|
||||
#include "CodecIncludes.h"
|
||||
#undef INCLUDE_MP3
|
||||
|
||||
bool FMP3_RuntimeCodec::CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData)
|
||||
{
|
||||
drmp3 MP3;
|
||||
|
||||
if (!drmp3_init_memory(&MP3, AudioData.GetView().GetData(), AudioData.GetView().Num(), nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drmp3_uninit(&MP3);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FMP3_RuntimeCodec::GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Retrieving header information for MP3 audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to retrieve audio header information in the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
drmp3 MP3;
|
||||
if (!drmp3_init_memory(&MP3, EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), nullptr))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to initialize MP3 Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const drmp3_uint64 PCMFrameCount = drmp3_get_pcm_frame_count(&MP3);
|
||||
|
||||
{
|
||||
HeaderInfo.Duration = static_cast<float>(PCMFrameCount) / MP3.sampleRate;
|
||||
HeaderInfo.NumOfChannels = MP3.channels;
|
||||
HeaderInfo.SampleRate = MP3.sampleRate;
|
||||
HeaderInfo.PCMDataSize = PCMFrameCount * MP3.channels;
|
||||
HeaderInfo.AudioFormat = GetAudioFormat();
|
||||
}
|
||||
|
||||
drmp3_uninit(&MP3);
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully retrieved header information for MP3 audio format.\nHeader info: %s"), *HeaderInfo.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FMP3_RuntimeCodec::Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality)
|
||||
{
|
||||
ensureMsgf(false, TEXT("MP3 codec does not support encoding at the moment"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FMP3_RuntimeCodec::Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Decoding MP3 audio data to uncompressed audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to decode audio data using the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
drmp3 MP3_Decoder;
|
||||
|
||||
// Initializing MP3 codec
|
||||
if (!drmp3_init_memory(&MP3_Decoder, EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), nullptr))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to initialize MP3 Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const drmp3_uint64 PCMFrameCount = drmp3_get_pcm_frame_count(&MP3_Decoder);
|
||||
|
||||
// Allocating memory for PCM data
|
||||
float* TempPCMData = static_cast<float*>(FMemory::Malloc(PCMFrameCount * MP3_Decoder.channels * sizeof(float)));
|
||||
|
||||
if (!TempPCMData)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to allocate memory for MP3 Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filling in PCM data and getting the number of frames
|
||||
DecodedData.PCMInfo.PCMNumOfFrames = drmp3_read_pcm_frames_f32(&MP3_Decoder, PCMFrameCount, TempPCMData);
|
||||
|
||||
// Getting PCM data size
|
||||
const int64 TempPCMDataSize = static_cast<int64>(DecodedData.PCMInfo.PCMNumOfFrames * MP3_Decoder.channels);
|
||||
|
||||
DecodedData.PCMInfo.PCMData = FRuntimeBulkDataBuffer<float>(TempPCMData, TempPCMDataSize);
|
||||
|
||||
// Getting basic audio information
|
||||
{
|
||||
DecodedData.SoundWaveBasicInfo.Duration = static_cast<float>(PCMFrameCount) / MP3_Decoder.sampleRate;
|
||||
DecodedData.SoundWaveBasicInfo.NumOfChannels = MP3_Decoder.channels;
|
||||
DecodedData.SoundWaveBasicInfo.SampleRate = MP3_Decoder.sampleRate;
|
||||
}
|
||||
|
||||
drmp3_uninit(&MP3_Decoder);
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully decoded MP3 audio data to uncompressed audio format.\nDecoded audio info: %s"), *DecodedData.ToString());
|
||||
return true;
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "Codecs/RuntimeCodecFactory.h"
|
||||
#include "Codecs/BaseRuntimeCodec.h"
|
||||
|
||||
#include "Codecs/MP3_RuntimeCodec.h"
|
||||
#include "Codecs/WAV_RuntimeCodec.h"
|
||||
#include "Codecs/FLAC_RuntimeCodec.h"
|
||||
#include "Codecs/VORBIS_RuntimeCodec.h"
|
||||
#include "Codecs/BINK_RuntimeCodec.h"
|
||||
|
||||
#include "Misc/Paths.h"
|
||||
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
|
||||
TUniquePtr<FBaseRuntimeCodec> FRuntimeCodecFactory::GetCodec(const FString& FilePath)
|
||||
{
|
||||
const FString Extension = FPaths::GetExtension(FilePath, false).ToLower();
|
||||
|
||||
if (Extension == TEXT("mp3"))
|
||||
{
|
||||
return MakeUnique<FMP3_RuntimeCodec>();
|
||||
}
|
||||
if (Extension == TEXT("wav") || Extension == TEXT("wave"))
|
||||
{
|
||||
return MakeUnique<FWAV_RuntimeCodec>();
|
||||
}
|
||||
if (Extension == TEXT("flac"))
|
||||
{
|
||||
return MakeUnique<FFLAC_RuntimeCodec>();
|
||||
}
|
||||
if (Extension == TEXT("ogg") || Extension == TEXT("oga") || Extension == TEXT("sb0"))
|
||||
{
|
||||
return MakeUnique<FVORBIS_RuntimeCodec>();
|
||||
}
|
||||
if (Extension == TEXT("bink") || Extension == TEXT("binka") || Extension == TEXT("bnk"))
|
||||
{
|
||||
return MakeUnique<FBINK_RuntimeCodec>();
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Warning, TEXT("Failed to determine the audio codec for '%s' using its file name"), *FilePath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TUniquePtr<FBaseRuntimeCodec> FRuntimeCodecFactory::GetCodec(ERuntimeAudioFormat AudioFormat)
|
||||
{
|
||||
switch (AudioFormat)
|
||||
{
|
||||
case ERuntimeAudioFormat::Mp3:
|
||||
return MakeUnique<FMP3_RuntimeCodec>();
|
||||
case ERuntimeAudioFormat::Wav:
|
||||
return MakeUnique<FWAV_RuntimeCodec>();
|
||||
case ERuntimeAudioFormat::Flac:
|
||||
return MakeUnique<FFLAC_RuntimeCodec>();
|
||||
case ERuntimeAudioFormat::OggVorbis:
|
||||
return MakeUnique<FVORBIS_RuntimeCodec>();
|
||||
case ERuntimeAudioFormat::Bink:
|
||||
return MakeUnique<FBINK_RuntimeCodec>();
|
||||
default:
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to determine the audio codec for the %s format"), *UEnum::GetValueAsString(AudioFormat));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TUniquePtr<FBaseRuntimeCodec> FRuntimeCodecFactory::GetCodec(const FRuntimeBulkDataBuffer<uint8>& AudioData)
|
||||
{
|
||||
TUniquePtr<FBaseRuntimeCodec> MP3_Codec = MakeUnique<FMP3_RuntimeCodec>();
|
||||
if (MP3_Codec->CheckAudioFormat(AudioData))
|
||||
{
|
||||
return MoveTemp(MP3_Codec);
|
||||
}
|
||||
|
||||
TUniquePtr<FBaseRuntimeCodec> FLAC_Codec = MakeUnique<FFLAC_RuntimeCodec>();
|
||||
if (FLAC_Codec->CheckAudioFormat(AudioData))
|
||||
{
|
||||
return MoveTemp(FLAC_Codec);
|
||||
}
|
||||
|
||||
TUniquePtr<FBaseRuntimeCodec> VORBIS_Codec = MakeUnique<FVORBIS_RuntimeCodec>();
|
||||
if (VORBIS_Codec->CheckAudioFormat(AudioData))
|
||||
{
|
||||
return MoveTemp(VORBIS_Codec);
|
||||
}
|
||||
|
||||
TUniquePtr<FBaseRuntimeCodec> BINK_Codec = MakeUnique<FBINK_RuntimeCodec>();
|
||||
if (BINK_Codec->CheckAudioFormat(AudioData))
|
||||
{
|
||||
return MoveTemp(BINK_Codec);
|
||||
}
|
||||
|
||||
TUniquePtr<FBaseRuntimeCodec> WAV_Codec = MakeUnique<FWAV_RuntimeCodec>();
|
||||
if (WAV_Codec->CheckAudioFormat(AudioData))
|
||||
{
|
||||
return MoveTemp(WAV_Codec);
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to determine the audio codec based on the audio data of size %lld bytes"), static_cast<int64>(AudioData.GetView().Num()));
|
||||
return nullptr;
|
||||
}
|
@ -0,0 +1,289 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "Codecs/VORBIS_RuntimeCodec.h"
|
||||
#include "RuntimeAudioImporterTypes.h"
|
||||
#include "Codecs/RAW_RuntimeCodec.h"
|
||||
#include "HAL/PlatformProperties.h"
|
||||
#include "HAL/UnrealMemory.h"
|
||||
|
||||
#include "VorbisAudioInfo.h"
|
||||
#include "Interfaces/IAudioFormat.h"
|
||||
#ifndef WITH_OGGVORBIS
|
||||
#define WITH_OGGVORBIS 0
|
||||
#endif
|
||||
|
||||
#define INCLUDE_VORBIS
|
||||
#include "CodecIncludes.h"
|
||||
#undef INCLUDE_VORBIS
|
||||
|
||||
bool FVORBIS_RuntimeCodec::CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData)
|
||||
{
|
||||
#if WITH_OGGVORBIS
|
||||
FVorbisAudioInfo AudioInfo;
|
||||
FSoundQualityInfo SoundQualityInfo;
|
||||
|
||||
if (!AudioInfo.ReadCompressedInfo(AudioData.GetView().GetData(), AudioData.GetView().Num(), &SoundQualityInfo) || SoundQualityInfo.SampleDataSize == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Your platform (%hs) does not support VORBIS decoding"), FPlatformProperties::IniPlatformName());
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FVORBIS_RuntimeCodec::GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Retrieving header information for VORBIS audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to retrieve audio header information in the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
#if WITH_OGGVORBIS
|
||||
FVorbisAudioInfo AudioInfo;
|
||||
FSoundQualityInfo SoundQualityInfo;
|
||||
|
||||
if (!AudioInfo.ReadCompressedInfo(EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), &SoundQualityInfo) || SoundQualityInfo.SampleDataSize == 0)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to read VORBIS compressed info"));
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
HeaderInfo.Duration = SoundQualityInfo.Duration;
|
||||
HeaderInfo.SampleRate = SoundQualityInfo.SampleRate;
|
||||
HeaderInfo.NumOfChannels = SoundQualityInfo.NumChannels;
|
||||
HeaderInfo.PCMDataSize = SoundQualityInfo.SampleDataSize / sizeof(int16);
|
||||
HeaderInfo.AudioFormat = GetAudioFormat();
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully retrieved header information for VORBIS audio format.\nHeader info: %s"), *HeaderInfo.ToString());
|
||||
return true;
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Your platform (%hs) does not support VORBIS decoding"), FPlatformProperties::IniPlatformName());
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FVORBIS_RuntimeCodec::Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Encoding uncompressed audio data to VORBIS audio format.\nDecoded audio info: %s.\nQuality: %d"), *DecodedData.ToString(), Quality);
|
||||
|
||||
#if PLATFORM_SUPPORTS_VORBIS_CODEC
|
||||
TArray<uint8> EncodedAudioData;
|
||||
|
||||
const uint32 NumOfFrames = DecodedData.PCMInfo.PCMNumOfFrames;
|
||||
const uint32 NumOfChannels = DecodedData.SoundWaveBasicInfo.NumOfChannels;
|
||||
const uint32 SampleRate = DecodedData.SoundWaveBasicInfo.SampleRate;
|
||||
|
||||
// Encoding Vorbis data
|
||||
{
|
||||
vorbis_info VorbisInfo;
|
||||
vorbis_info_init(&VorbisInfo);
|
||||
|
||||
if (vorbis_encode_init_vbr(&VorbisInfo, NumOfChannels, SampleRate, static_cast<float>(Quality) / 100) < 0)
|
||||
{
|
||||
vorbis_info_clear(&VorbisInfo);
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to initialize VORBIS encoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the comment
|
||||
vorbis_comment VorbisComment;
|
||||
{
|
||||
vorbis_comment_init(&VorbisComment);
|
||||
vorbis_comment_add_tag(&VorbisComment, "ENCODER", "RuntimeAudioImporter");
|
||||
}
|
||||
|
||||
// Start making a vorbis block
|
||||
vorbis_dsp_state VorbisDspState;
|
||||
vorbis_block VorbisBlock;
|
||||
{
|
||||
vorbis_analysis_init(&VorbisDspState, &VorbisInfo);
|
||||
vorbis_block_init(&VorbisDspState, &VorbisBlock);
|
||||
}
|
||||
|
||||
// Ogg packet stuff
|
||||
ogg_packet OggPacket, OggComment, OggCode;
|
||||
ogg_page OggPage;
|
||||
ogg_stream_state OggStreamState;
|
||||
|
||||
{
|
||||
ogg_stream_init(&OggStreamState, 0);
|
||||
vorbis_analysis_headerout(&VorbisDspState, &VorbisComment, &OggPacket, &OggComment, &OggCode);
|
||||
ogg_stream_packetin(&OggStreamState, &OggPacket);
|
||||
ogg_stream_packetin(&OggStreamState, &OggComment);
|
||||
ogg_stream_packetin(&OggStreamState, &OggCode);
|
||||
}
|
||||
|
||||
auto CleanUpVORBIS = [&]()
|
||||
{
|
||||
ogg_stream_clear(&OggStreamState);
|
||||
vorbis_block_clear(&VorbisBlock);
|
||||
vorbis_dsp_clear(&VorbisDspState);
|
||||
vorbis_comment_clear(&VorbisComment);
|
||||
vorbis_info_clear(&VorbisInfo);
|
||||
};
|
||||
|
||||
// Do stuff until we don't do stuff anymore since we need the data on a separate page
|
||||
while (ogg_stream_flush(&OggStreamState, &OggPage))
|
||||
{
|
||||
EncodedAudioData.Append(static_cast<uint8*>(OggPage.header), OggPage.header_len);
|
||||
EncodedAudioData.Append(static_cast<uint8*>(OggPage.body), OggPage.body_len);
|
||||
}
|
||||
|
||||
uint32 FramesEncoded{0}, FramesRead{0};
|
||||
|
||||
bool bEndOfBitstream{false};
|
||||
|
||||
while (!bEndOfBitstream)
|
||||
{
|
||||
constexpr uint32 FramesSplitCount = 1024;
|
||||
|
||||
// Getting frames for encoding
|
||||
uint32 FramesToEncode{NumOfFrames - FramesRead};
|
||||
|
||||
// Analyze buffers
|
||||
float** AnalysisBuffer = vorbis_analysis_buffer(&VorbisDspState, FramesToEncode);
|
||||
|
||||
// Make sure we don't read more than FramesSplitCount, since libvorbis can segfault if we read too much at once
|
||||
if (FramesToEncode > FramesSplitCount)
|
||||
{
|
||||
FramesToEncode = FramesSplitCount;
|
||||
}
|
||||
|
||||
if (!DecodedData.PCMInfo.PCMData.GetView().GetData() || !AnalysisBuffer)
|
||||
{
|
||||
CleanUpVORBIS();
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to create VORBIS analysis buffers"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deinterleave for the encoder
|
||||
for (uint32 FrameIndex = 0; FrameIndex < FramesToEncode; ++FrameIndex)
|
||||
{
|
||||
const float* Frame = DecodedData.PCMInfo.PCMData.GetView().GetData() + (FrameIndex + FramesEncoded) * NumOfChannels;
|
||||
|
||||
for (uint32 ChannelIndex = 0; ChannelIndex < NumOfChannels; ++ChannelIndex)
|
||||
{
|
||||
AnalysisBuffer[ChannelIndex][FrameIndex] = Frame[ChannelIndex];
|
||||
}
|
||||
}
|
||||
|
||||
// Set how many frames we wrote
|
||||
if (vorbis_analysis_wrote(&VorbisDspState, FramesToEncode) < 0)
|
||||
{
|
||||
CleanUpVORBIS();
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to read VORBIS frames"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Separate AnalysisBuffer into separate blocks, then chunk those blocks into pages
|
||||
while (vorbis_analysis_blockout(&VorbisDspState, &VorbisBlock) == 1)
|
||||
{
|
||||
// Perform actual analysis
|
||||
vorbis_analysis(&VorbisBlock, nullptr);
|
||||
|
||||
// Determine the bitrate on this block
|
||||
vorbis_bitrate_addblock(&VorbisBlock);
|
||||
|
||||
// Flush all available vorbis blocks into packets, then append the resulting pages to the output buffer
|
||||
while (vorbis_bitrate_flushpacket(&VorbisDspState, &OggPacket))
|
||||
{
|
||||
ogg_stream_packetin(&OggStreamState, &OggPacket);
|
||||
|
||||
while (!bEndOfBitstream)
|
||||
{
|
||||
// Write data if we have a page
|
||||
if (!ogg_stream_pageout(&OggStreamState, &OggPage))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
EncodedAudioData.Append(static_cast<uint8*>(OggPage.header), OggPage.header_len);
|
||||
EncodedAudioData.Append(static_cast<uint8*>(OggPage.body), OggPage.body_len);
|
||||
|
||||
// End if we need to
|
||||
if (ogg_page_eos(&OggPage))
|
||||
{
|
||||
bEndOfBitstream = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Increment
|
||||
FramesEncoded += FramesToEncode;
|
||||
FramesRead += FramesToEncode;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
CleanUpVORBIS();
|
||||
}
|
||||
|
||||
// Populating the encoded audio data
|
||||
{
|
||||
EncodedData.AudioData = FRuntimeBulkDataBuffer<uint8>(EncodedAudioData);
|
||||
EncodedData.AudioFormat = ERuntimeAudioFormat::OggVorbis;
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully encoded uncompressed audio data to VORBIS audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
return true;
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Your platform (%hs) does not support VORBIS encoding"), FPlatformProperties::IniPlatformName());
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FVORBIS_RuntimeCodec::Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Decoding VORBIS audio data to uncompressed audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to decode audio data using the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
#if WITH_OGGVORBIS
|
||||
FVorbisAudioInfo AudioInfo;
|
||||
FSoundQualityInfo SoundQualityInfo;
|
||||
|
||||
// Parse the audio header for the relevant information
|
||||
if (!AudioInfo.ReadCompressedInfo(EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), &SoundQualityInfo))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to read VORBIS compressed info"));
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray64<uint8> PCMData;
|
||||
|
||||
// Decompress all the sample data
|
||||
PCMData.Empty(SoundQualityInfo.SampleDataSize);
|
||||
PCMData.AddZeroed(SoundQualityInfo.SampleDataSize);
|
||||
AudioInfo.ExpandFile(PCMData.GetData(), &SoundQualityInfo);
|
||||
|
||||
// Getting the number of frames
|
||||
DecodedData.PCMInfo.PCMNumOfFrames = PCMData.Num() / SoundQualityInfo.NumChannels / sizeof(int16);
|
||||
|
||||
const int64 NumOfSamples = PCMData.Num() / sizeof(int16);
|
||||
|
||||
// Transcoding int16 to float format
|
||||
{
|
||||
float* TempFloatBuffer;
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<int16, float>(reinterpret_cast<int16*>(PCMData.GetData()), NumOfSamples, TempFloatBuffer);
|
||||
DecodedData.PCMInfo.PCMData = FRuntimeBulkDataBuffer<float>(TempFloatBuffer, NumOfSamples);
|
||||
}
|
||||
|
||||
// Getting basic audio information
|
||||
{
|
||||
DecodedData.SoundWaveBasicInfo.Duration = SoundQualityInfo.Duration;
|
||||
DecodedData.SoundWaveBasicInfo.NumOfChannels = SoundQualityInfo.NumChannels;
|
||||
DecodedData.SoundWaveBasicInfo.SampleRate = SoundQualityInfo.SampleRate;
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully decoded VORBIS audio data to uncompressed audio format.\nDecoded audio info: %s"), *DecodedData.ToString());
|
||||
return true;
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Your platform (%hs) does not support VORBIS decoding"), FPlatformProperties::IniPlatformName());
|
||||
#endif
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "Codecs/WAV_RuntimeCodec.h"
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
#include "RuntimeAudioImporterTypes.h"
|
||||
#include "HAL/UnrealMemory.h"
|
||||
|
||||
#define INCLUDE_WAV
|
||||
#include "CodecIncludes.h"
|
||||
#include "Codecs/RAW_RuntimeCodec.h"
|
||||
#undef INCLUDE_WAV
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* Check and fix the WAV audio data with the correct byte size in the RIFF container
|
||||
* Made by https://github.com/kass-kass
|
||||
*/
|
||||
bool CheckAndFixWavDurationErrors(const FRuntimeBulkDataBuffer<uint8>& WavData)
|
||||
{
|
||||
drwav WAV;
|
||||
|
||||
// Initializing WAV codec
|
||||
if (!drwav_init_memory(&WAV, WavData.GetView().GetData(), WavData.GetView().Num(), nullptr))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to initialize WAV Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the container is RIFF (not Wave64 or any other containers)
|
||||
if (WAV.container != drwav_container_riff)
|
||||
{
|
||||
drwav_uninit(&WAV);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get 4-byte field at byte 4, which is the overall file size as uint32, according to RIFF specification.
|
||||
// If the field is set to nothing (hex FFFFFFFF), replace the incorrectly set field with the actual size.
|
||||
// The field should be (size of file - 8 bytes), as the chunk identifier for the whole file (4 bytes spelling out RIFF at the start of the file), and the chunk length (4 bytes that we're replacing) are excluded.
|
||||
if (BytesToHex(WavData.GetView().GetData() + 4, 4) == "FFFFFFFF")
|
||||
{
|
||||
const int32 ActualFileSize = WavData.GetView().Num() - 8;
|
||||
FMemory::Memcpy(WavData.GetView().GetData() + 4, &ActualFileSize, 4);
|
||||
}
|
||||
|
||||
// Search for the place in the file after the chunk id "data", which is where the data length is stored.
|
||||
// First 36 bytes are skipped, as they're always "RIFF", 4 bytes filesize, "WAVE", "fmt ", and 20 bytes of format data.
|
||||
uint32 DataSizeLocation = INDEX_NONE;
|
||||
for (uint32 Index = 36; Index < static_cast<uint32>(WavData.GetView().Num()) - 4; ++Index)
|
||||
{
|
||||
// "64617461" - hex for string "data"
|
||||
if (BytesToHex(WavData.GetView().GetData() + Index, 4) == "64617461")
|
||||
{
|
||||
DataSizeLocation = Index + 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Should never happen, but just in case
|
||||
if (DataSizeLocation == INDEX_NONE)
|
||||
{
|
||||
drwav_uninit(&WAV);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Same process as replacing full file size, except DataSize counts bytes from end of DataSize int to end of file.
|
||||
if (BytesToHex(WavData.GetView().GetData() + DataSizeLocation, 4) == "FFFFFFFF")
|
||||
{
|
||||
// -4 to not include the DataSize int itself
|
||||
const uint32 ActualDataSize = WavData.GetView().Num() - DataSizeLocation - 4;
|
||||
|
||||
FMemory::Memcpy(WavData.GetView().GetData() + DataSizeLocation, &ActualDataSize, 4);
|
||||
}
|
||||
|
||||
drwav_uninit(&WAV);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool FWAV_RuntimeCodec::CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData)
|
||||
{
|
||||
drwav WAV;
|
||||
|
||||
CheckAndFixWavDurationErrors(AudioData);
|
||||
|
||||
if (!drwav_init_memory(&WAV, AudioData.GetView().GetData(), AudioData.GetView().Num(), nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
drwav_uninit(&WAV);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FWAV_RuntimeCodec::GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Retrieving header information for WAV audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to retrieve audio header information in the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
drwav WAV;
|
||||
if (!drwav_init_memory(&WAV, EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), nullptr))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to initialize WAV Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
HeaderInfo.Duration = static_cast<float>(WAV.totalPCMFrameCount) / WAV.sampleRate;
|
||||
HeaderInfo.NumOfChannels = WAV.channels;
|
||||
HeaderInfo.SampleRate = WAV.sampleRate;
|
||||
HeaderInfo.PCMDataSize = WAV.totalPCMFrameCount * WAV.channels;
|
||||
HeaderInfo.AudioFormat = GetAudioFormat();
|
||||
}
|
||||
|
||||
drwav_uninit(&WAV);
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully retrieved header information for WAV audio format.\nHeader info: %s"), *HeaderInfo.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FWAV_RuntimeCodec::Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Encoding uncompressed audio data to WAV audio format.\nDecoded audio info: %s."), *DecodedData.ToString());
|
||||
|
||||
drwav WAV_Encoder;
|
||||
|
||||
drwav_data_format WAV_Format;
|
||||
{
|
||||
WAV_Format.container = drwav_container_riff;
|
||||
WAV_Format.format = DR_WAVE_FORMAT_PCM;
|
||||
WAV_Format.channels = DecodedData.SoundWaveBasicInfo.NumOfChannels;
|
||||
WAV_Format.sampleRate = DecodedData.SoundWaveBasicInfo.SampleRate;
|
||||
WAV_Format.bitsPerSample = 16;
|
||||
}
|
||||
|
||||
void* CompressedData = nullptr;
|
||||
size_t CompressedDataLen;
|
||||
|
||||
if (!drwav_init_memory_write(&WAV_Encoder, &CompressedData, &CompressedDataLen, &WAV_Format, nullptr))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to initialize WAV Encoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
int16* TempInt16BBuffer;
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<float, int16>(DecodedData.PCMInfo.PCMData.GetView().GetData(), DecodedData.PCMInfo.PCMData.GetView().Num(), TempInt16BBuffer);
|
||||
|
||||
drwav_write_pcm_frames(&WAV_Encoder, DecodedData.PCMInfo.PCMNumOfFrames, TempInt16BBuffer);
|
||||
drwav_uninit(&WAV_Encoder);
|
||||
FMemory::Free(TempInt16BBuffer);
|
||||
|
||||
// Populating the encoded audio data
|
||||
{
|
||||
EncodedData.AudioData = FRuntimeBulkDataBuffer<uint8>(static_cast<uint8*>(CompressedData), static_cast<int64>(CompressedDataLen));
|
||||
EncodedData.AudioFormat = ERuntimeAudioFormat::Wav;
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully encoded uncompressed audio data to WAV audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FWAV_RuntimeCodec::Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Decoding WAV audio data to uncompressed audio format.\nEncoded audio info: %s"), *EncodedData.ToString());
|
||||
|
||||
ensureAlwaysMsgf(EncodedData.AudioFormat == GetAudioFormat(), TEXT("Attempting to decode audio data using the '%s' codec, but the data format is encoded in '%s'"),
|
||||
*UEnum::GetValueAsString(GetAudioFormat()), *UEnum::GetValueAsString(EncodedData.AudioFormat));
|
||||
|
||||
if (!CheckAndFixWavDurationErrors(EncodedData.AudioData))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Something went wrong while fixing WAV audio data duration error"));
|
||||
return false;
|
||||
}
|
||||
|
||||
drwav WAV_Decoder;
|
||||
|
||||
// Initializing WAV codec
|
||||
if (!drwav_init_memory(&WAV_Decoder, EncodedData.AudioData.GetView().GetData(), EncodedData.AudioData.GetView().Num(), nullptr))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to initialize WAV Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocating memory for PCM data
|
||||
float* TempPCMData = static_cast<float*>(FMemory::Malloc(WAV_Decoder.totalPCMFrameCount * WAV_Decoder.channels * sizeof(float)));
|
||||
if (!TempPCMData)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to allocate memory for WAV Decoder"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filling PCM data and getting the number of frames
|
||||
DecodedData.PCMInfo.PCMNumOfFrames = drwav_read_pcm_frames_f32(&WAV_Decoder, WAV_Decoder.totalPCMFrameCount, TempPCMData);
|
||||
|
||||
// Getting PCM data size
|
||||
const int64 TempPCMDataSize = static_cast<int64>(DecodedData.PCMInfo.PCMNumOfFrames * WAV_Decoder.channels);
|
||||
|
||||
DecodedData.PCMInfo.PCMData = FRuntimeBulkDataBuffer<float>(TempPCMData, TempPCMDataSize);
|
||||
|
||||
// Getting basic audio information
|
||||
{
|
||||
DecodedData.SoundWaveBasicInfo.Duration = static_cast<float>(WAV_Decoder.totalPCMFrameCount) / WAV_Decoder.sampleRate;
|
||||
DecodedData.SoundWaveBasicInfo.NumOfChannels = WAV_Decoder.channels;
|
||||
DecodedData.SoundWaveBasicInfo.SampleRate = WAV_Decoder.sampleRate;
|
||||
}
|
||||
|
||||
drwav_uninit(&WAV_Decoder);
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully decoded WAV audio data to uncompressed audio format.\nDecoded audio info: %s"), *DecodedData.ToString());
|
||||
return true;
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
#include "MetaSound/MetasoundImportedWave.h"
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
|
||||
namespace RuntimeAudioImporter
|
||||
{
|
||||
const FString PluginAuthor = TEXT("Georgy Treshchev");
|
||||
const FText PluginNodeMissingPrompt = NSLOCTEXT("RuntimeAudioImporter", "DefaultMissingNodePrompt", "The node was likely removed, renamed, or the RuntimeAudioImporter plugin is not loaded.");
|
||||
|
||||
FImportedWave::FImportedWave(const TUniquePtr<Audio::IProxyData>& InInitData)
|
||||
{
|
||||
if (InInitData.IsValid())
|
||||
{
|
||||
if (InInitData->CheckTypeCast<FSoundWaveProxy>())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully retrieved proxy data from Imported Sound Wave"));
|
||||
SoundWaveProxy = MakeShared<FSoundWaveProxy, ESPMode::ThreadSafe>(InInitData->GetAs<FSoundWaveProxy>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,148 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
#include "CoreMinimal.h"
|
||||
#include "Internationalization/Text.h"
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
|
||||
#include "MetasoundWave.h"
|
||||
#include "MetasoundFacade.h"
|
||||
#include "MetasoundParamHelper.h"
|
||||
#include "MetasoundExecutableOperator.h"
|
||||
#include "MetasoundNodeRegistrationMacro.h"
|
||||
#include "MetasoundStandardNodesCategories.h"
|
||||
#include "MetasoundDataTypeRegistrationMacro.h"
|
||||
#include "MetasoundVertex.h"
|
||||
#include "MetaSound/MetasoundImportedWave.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "MetasoundImportedWaveToWaveAsset"
|
||||
|
||||
namespace RuntimeAudioImporter
|
||||
{
|
||||
namespace ImportedWaveToWaveAssetNodeParameterNames
|
||||
{
|
||||
METASOUND_PARAM(ParamImportedWave, "Imported Wave", "Input Imported Wave");
|
||||
METASOUND_PARAM(ParamWaveAsset, "Wave Asset", "Output Wave Asset");
|
||||
}
|
||||
|
||||
class FImportedWaveToWaveAssetNodeOperator : public Metasound::TExecutableOperator<FImportedWaveToWaveAssetNodeOperator>
|
||||
{
|
||||
public:
|
||||
FImportedWaveToWaveAssetNodeOperator(const FImportedWaveReadRef& InImportedWave)
|
||||
: ImportedWave(InImportedWave)
|
||||
, WaveAsset(Metasound::FWaveAssetWriteRef::CreateNew(nullptr))
|
||||
{
|
||||
Execute();
|
||||
}
|
||||
|
||||
static const Metasound::FNodeClassMetadata& GetNodeInfo()
|
||||
{
|
||||
auto InitNodeInfo = []() -> Metasound::FNodeClassMetadata
|
||||
{
|
||||
Metasound::FNodeClassMetadata Metadata
|
||||
{
|
||||
Metasound::FNodeClassName{"RuntimeAudioImporter", "ImportedWaveToWaveAsset", ""},
|
||||
1, // Major Version
|
||||
0, // Minor Version
|
||||
LOCTEXT("ImportedWaveToWaveAssetNode_Name", "Imported Wave To Wave Asset"),
|
||||
LOCTEXT("ImportedWaveToWaveAssetNode_Description", "Converts Imported Wave to Wave Asset parameter."),
|
||||
RuntimeAudioImporter::PluginAuthor,
|
||||
RuntimeAudioImporter::PluginNodeMissingPrompt,
|
||||
GetDefaultInterface(),
|
||||
{METASOUND_LOCTEXT("RuntimeAudioImporter_Metasound_Category", "RuntimeAudioImporter")},
|
||||
{
|
||||
METASOUND_LOCTEXT("RuntimeAudioImporter_Metasound_Keyword", "RuntimeAudioImported"),
|
||||
METASOUND_LOCTEXT("ImportedSoundWave_Metasound_Keyword", "ImportedSoundWave")
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
return Metadata;
|
||||
};
|
||||
|
||||
static const Metasound::FNodeClassMetadata Info = InitNodeInfo();
|
||||
|
||||
return Info;
|
||||
}
|
||||
|
||||
static const Metasound::FVertexInterface& GetDefaultInterface()
|
||||
{
|
||||
using namespace Metasound;
|
||||
using namespace ImportedWaveToWaveAssetNodeParameterNames;
|
||||
|
||||
static const FVertexInterface DefaultInterface(
|
||||
FInputVertexInterface(TInputDataVertex<FImportedWave>(METASOUND_GET_PARAM_NAME_AND_METADATA(ParamImportedWave))
|
||||
),
|
||||
FOutputVertexInterface(TOutputDataVertex<FWaveAsset>(METASOUND_GET_PARAM_NAME_AND_METADATA(ParamWaveAsset))
|
||||
)
|
||||
);
|
||||
|
||||
return DefaultInterface;
|
||||
}
|
||||
|
||||
static TUniquePtr<IOperator> CreateOperator(const Metasound::FCreateOperatorParams& InParams, Metasound::FBuildErrorArray& OutErrors)
|
||||
{
|
||||
using namespace Metasound;
|
||||
using namespace ImportedWaveToWaveAssetNodeParameterNames;
|
||||
|
||||
const FDataReferenceCollection& InputDataRefs = InParams.InputDataReferences;
|
||||
|
||||
FImportedWaveReadRef ImportedWaveIn = InputDataRefs.GetDataReadReferenceOrConstruct<FImportedWave>(METASOUND_GET_PARAM_NAME(ParamImportedWave));
|
||||
|
||||
return MakeUnique<FImportedWaveToWaveAssetNodeOperator>(ImportedWaveIn);
|
||||
}
|
||||
|
||||
virtual Metasound::FDataReferenceCollection GetInputs() const override
|
||||
{
|
||||
using namespace Metasound;
|
||||
using namespace ImportedWaveToWaveAssetNodeParameterNames;
|
||||
|
||||
FDataReferenceCollection InputDataReferences;
|
||||
InputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(ParamImportedWave), FImportedWaveReadRef(ImportedWave));
|
||||
|
||||
return InputDataReferences;
|
||||
}
|
||||
|
||||
virtual Metasound::FDataReferenceCollection GetOutputs() const override
|
||||
{
|
||||
using namespace Metasound;
|
||||
using namespace ImportedWaveToWaveAssetNodeParameterNames;
|
||||
|
||||
FDataReferenceCollection OutputDataReferences;
|
||||
OutputDataReferences.AddDataReadReference(METASOUND_GET_PARAM_NAME(ParamWaveAsset), FWaveAssetWriteRef(WaveAsset));
|
||||
|
||||
return OutputDataReferences;
|
||||
}
|
||||
|
||||
void Execute()
|
||||
{
|
||||
using namespace Metasound;
|
||||
if (ImportedWave->IsSoundWaveValid())
|
||||
{
|
||||
if (!WaveAsset->GetSoundWaveProxy().IsValid())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully transmitted proxy data from Imported Wave to Wave Asset"));
|
||||
*WaveAsset = FWaveAsset(MakeShared<FSoundWaveProxy>(*ImportedWave->GetSoundWaveProxy().Get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
FImportedWaveReadRef ImportedWave;
|
||||
Metasound::FWaveAssetWriteRef WaveAsset;
|
||||
};
|
||||
|
||||
class FImportedWaveToWaveAssetNode : public Metasound::FNodeFacade
|
||||
{
|
||||
public:
|
||||
FImportedWaveToWaveAssetNode(const Metasound::FNodeInitData& InInitData)
|
||||
: FNodeFacade(InInitData.InstanceName, InInitData.InstanceID, Metasound::TFacadeOperatorClass<FImportedWaveToWaveAssetNodeOperator>())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
METASOUND_REGISTER_NODE(FImportedWaveToWaveAssetNode);
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
#endif
|
@ -0,0 +1,9 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "PreImportedSoundAsset.h"
|
||||
|
||||
|
||||
UPreImportedSoundAsset::UPreImportedSoundAsset()
|
||||
: AudioFormat(ERuntimeAudioFormat::Mp3)
|
||||
{
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "RuntimeAudioImporter.h"
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FRuntimeAudioImporterModule"
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
#include "MetasoundDataTypeRegistrationMacro.h"
|
||||
#include "MetasoundFrontendRegistries.h"
|
||||
#include "MetasoundWave.h"
|
||||
#include "MetaSound/MetasoundImportedWave.h"
|
||||
#include "Sound/ImportedSoundWave.h"
|
||||
|
||||
REGISTER_METASOUND_DATATYPE(RuntimeAudioImporter::FImportedWave, "ImportedWave", Metasound::ELiteralType::UObjectProxy, UImportedSoundWave);
|
||||
#endif
|
||||
|
||||
void FRuntimeAudioImporterModule::StartupModule()
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
FModuleManager::Get().LoadModuleChecked("MetasoundEngine");
|
||||
FMetasoundFrontendRegistryContainer::Get()->RegisterPendingNodes();
|
||||
#endif
|
||||
}
|
||||
|
||||
void FRuntimeAudioImporterModule::ShutdownModule()
|
||||
{
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FRuntimeAudioImporterModule, RuntimeAudioImporter)
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogRuntimeAudioImporter);
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,205 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "Sound/CapturableSoundWave.h"
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
#include "AudioThread.h"
|
||||
#include "Async/Async.h"
|
||||
|
||||
UCapturableSoundWave::UCapturableSoundWave(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
void UCapturableSoundWave::BeginDestroy()
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
AudioCapture.AbortStream();
|
||||
AudioCapture.CloseStream();
|
||||
#endif
|
||||
|
||||
Super::BeginDestroy();
|
||||
}
|
||||
|
||||
UCapturableSoundWave* UCapturableSoundWave::CreateCapturableSoundWave()
|
||||
{
|
||||
if (!IsInGameThread())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to create a sound wave outside of the game thread"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return NewObject<UCapturableSoundWave>();
|
||||
}
|
||||
|
||||
void UCapturableSoundWave::GetAvailableAudioInputDevices(const FOnGetAvailableAudioInputDevicesResult& Result)
|
||||
{
|
||||
GetAvailableAudioInputDevices(FOnGetAvailableAudioInputDevicesResultNative::CreateLambda([Result](const TArray<FRuntimeAudioInputDeviceInfo>& AvailableDevices)
|
||||
{
|
||||
Result.ExecuteIfBound(AvailableDevices);
|
||||
}));
|
||||
}
|
||||
|
||||
void UCapturableSoundWave::GetAvailableAudioInputDevices(const FOnGetAvailableAudioInputDevicesResultNative& Result)
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
if (!IsInAudioThread())
|
||||
{
|
||||
FAudioThread::RunCommandOnAudioThread([Result]()
|
||||
{
|
||||
GetAvailableAudioInputDevices(Result);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto ExecuteResult = [Result](const TArray<FRuntimeAudioInputDeviceInfo>& AvailableDevices)
|
||||
{
|
||||
AsyncTask(ENamedThreads::GameThread, [Result, AvailableDevices]()
|
||||
{
|
||||
Result.ExecuteIfBound(AvailableDevices);
|
||||
});
|
||||
};
|
||||
|
||||
TArray<FRuntimeAudioInputDeviceInfo> AvailableDevices;
|
||||
|
||||
Audio::FAudioCapture AudioCapture;
|
||||
TArray<Audio::FCaptureDeviceInfo> InputDevices;
|
||||
|
||||
AudioCapture.GetCaptureDevicesAvailable(InputDevices);
|
||||
|
||||
for (const Audio::FCaptureDeviceInfo& CaptureDeviceInfo : InputDevices)
|
||||
{
|
||||
AvailableDevices.Add(FRuntimeAudioInputDeviceInfo(CaptureDeviceInfo));
|
||||
}
|
||||
|
||||
ExecuteResult(AvailableDevices);
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to get available audio input devices as its support is disabled (please enable in RuntimeAudioImporter.Build.cs)"));
|
||||
Result.ExecuteIfBound(TArray<FRuntimeAudioInputDeviceInfo>());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool UCapturableSoundWave::StartCapture(int32 DeviceId)
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
if (AudioCapture.IsStreamOpen())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to start capture as the stream is already open"));
|
||||
return false;
|
||||
}
|
||||
|
||||
Audio::FOnCaptureFunction OnCapture = [this](const float* PCMData, int32 NumFrames, int32 NumOfChannels,
|
||||
#if UE_VERSION_NEWER_THAN(4, 25, 0)
|
||||
int32 InSampleRate,
|
||||
#endif
|
||||
double StreamTime, bool bOverFlow)
|
||||
{
|
||||
if (AudioCapture.IsCapturing())
|
||||
{
|
||||
const int64 PCMDataSize = NumOfChannels * NumFrames;
|
||||
const int64 PCMDataSizeInBytes = PCMDataSize * sizeof(float);
|
||||
|
||||
if (PCMDataSizeInBytes > TNumericLimits<int32>::Max())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to append audio data as the size of the data exceeds the maximum size of int32 (PCMDataSizeInBytes: %lld, Max: %d)"), PCMDataSizeInBytes, TNumericLimits<int32>::Max());
|
||||
return;
|
||||
}
|
||||
|
||||
AppendAudioDataFromRAW(TArray<uint8>(reinterpret_cast<const uint8*>(PCMData), static_cast<int32>(PCMDataSizeInBytes)), ERuntimeRAWAudioFormat::Float32,
|
||||
#if UE_VERSION_NEWER_THAN(4, 25, 0)
|
||||
InSampleRate
|
||||
#else
|
||||
AudioCapture.GetSampleRate()
|
||||
#endif
|
||||
, NumOfChannels);
|
||||
}
|
||||
};
|
||||
|
||||
Audio::FAudioCaptureDeviceParams Params = Audio::FAudioCaptureDeviceParams();
|
||||
Params.DeviceIndex = DeviceId;
|
||||
|
||||
if (!AudioCapture.OpenCaptureStream(Params, MoveTemp(OnCapture), 1024))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to open capturing stream for sound wave %s"), *GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AudioCapture.StartStream())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to start capturing for sound wave %s"), *GetName());
|
||||
return false;
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully started capturing for sound wave %s"), *GetName());
|
||||
return true;
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to start capturing as its support is disabled (please enable in RuntimeAudioImporter.Build.cs)"));
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void UCapturableSoundWave::StopCapture()
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
if (AudioCapture.IsStreamOpen())
|
||||
{
|
||||
AudioCapture.CloseStream();
|
||||
}
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to stop capturing as its support is disabled (please enable in RuntimeAudioImporter.Build.cs)"));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool UCapturableSoundWave::ToggleMute(bool bMute)
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
if (!AudioCapture.IsStreamOpen())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to toggle mute for %s as the stream is not open"), *GetName());
|
||||
return false;
|
||||
}
|
||||
if (bMute)
|
||||
{
|
||||
if (!AudioCapture.IsCapturing())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to mute as the stream for %s is already closed"), *GetName());
|
||||
return false;
|
||||
}
|
||||
if (!AudioCapture.StopStream())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to mute the stream for sound wave %s"), *GetName());
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully muted the stream for sound wave %s"), *GetName());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AudioCapture.IsCapturing())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to unmute as the stream for %s is already open"), *GetName());
|
||||
return false;
|
||||
}
|
||||
if (!AudioCapture.StartStream())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to unmute the stream for sound wave %s"), *GetName());
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully unmuted the stream for sound wave %s"), *GetName());
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to toggle mute as its support is disabled (please enable in RuntimeAudioImporter.Build.cs)"));
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool UCapturableSoundWave::IsCapturing() const
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
return AudioCapture.IsCapturing();
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to get capturing state as its support is disabled (please enable in RuntimeAudioImporter.Build.cs)"));
|
||||
return false;
|
||||
#endif
|
||||
}
|
@ -0,0 +1,667 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "Sound/ImportedSoundWave.h"
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
#include "AudioDevice.h"
|
||||
#include "Async/Async.h"
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
#include "Codecs/VORBIS_RuntimeCodec.h"
|
||||
#endif
|
||||
#include "Codecs/RAW_RuntimeCodec.h"
|
||||
#include "UObject/GCObjectScopeGuard.h"
|
||||
|
||||
UImportedSoundWave::UImportedSoundWave(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
, DurationOffset(0)
|
||||
, PlaybackFinishedBroadcast(false)
|
||||
, PlayedNumOfFrames(0)
|
||||
, PCMBufferInfo(MakeUnique<FPCMStruct>())
|
||||
, bStopSoundOnPlaybackFinish(true)
|
||||
{
|
||||
ensure(PCMBufferInfo);
|
||||
|
||||
#if UE_VERSION_NEWER_THAN(5, 0, 0)
|
||||
SetImportedSampleRate(0);
|
||||
#endif
|
||||
SetSampleRate(0);
|
||||
NumChannels = 0;
|
||||
Duration = 0;
|
||||
bProcedural = true;
|
||||
DecompressionType = EDecompressionType::DTYPE_Procedural;
|
||||
SoundGroup = ESoundGroup::SOUNDGROUP_Default;
|
||||
SetPrecacheState(ESoundWavePrecacheState::Done);
|
||||
}
|
||||
|
||||
UImportedSoundWave* UImportedSoundWave::CreateImportedSoundWave()
|
||||
{
|
||||
if (!IsInGameThread())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to create a sound wave outside of the game thread"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return NewObject<UImportedSoundWave>();
|
||||
}
|
||||
|
||||
Audio::EAudioMixerStreamDataFormat::Type UImportedSoundWave::GetGeneratedPCMDataFormat() const
|
||||
{
|
||||
return Audio::EAudioMixerStreamDataFormat::Type::Float;
|
||||
}
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
TSharedPtr<Audio::IProxyData> UImportedSoundWave::CreateProxyData(const Audio::FProxyDataInitParams& InitParams)
|
||||
{
|
||||
if (SoundWaveDataPtr)
|
||||
{
|
||||
SoundWaveDataPtr->OverrideRuntimeFormat(Audio::NAME_OGG);
|
||||
}
|
||||
return USoundWave::CreateProxyData(InitParams);
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::InitAudioResource(FName Format)
|
||||
{
|
||||
// Only OGG format is supported for audio resource initialization
|
||||
if (Format != Audio::NAME_OGG)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("RuntimeAudioImporter does not support audio format '%s' for initialization. Supported format: %s"), *Format.ToString(), *Audio::NAME_OGG.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SoundWaveDataPtr->GetResourceSize() > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
FDecodedAudioStruct DecodedAudioInfo;
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
{
|
||||
DecodedAudioInfo.PCMInfo = GetPCMBuffer();
|
||||
FSoundWaveBasicStruct SoundWaveBasicInfo;
|
||||
{
|
||||
SoundWaveBasicInfo.NumOfChannels = NumChannels;
|
||||
SoundWaveBasicInfo.SampleRate = GetSampleRate();
|
||||
SoundWaveBasicInfo.Duration = Duration;
|
||||
}
|
||||
DecodedAudioInfo.SoundWaveBasicInfo = SoundWaveBasicInfo;
|
||||
}
|
||||
}
|
||||
|
||||
FVORBIS_RuntimeCodec VorbisCodec;
|
||||
FEncodedAudioStruct EncodedAudioInfo;
|
||||
if (!VorbisCodec.Encode(MoveTemp(DecodedAudioInfo), EncodedAudioInfo, 100))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Something went wrong while encoding Vorbis audio data"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EncodedAudioInfo.AudioData.GetView().Num() <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FByteBulkData CompressedBulkData;
|
||||
|
||||
// Filling in the compressed data
|
||||
{
|
||||
CompressedBulkData.Lock(LOCK_READ_WRITE);
|
||||
FMemory::Memcpy(CompressedBulkData.Realloc(EncodedAudioInfo.AudioData.GetView().Num()), EncodedAudioInfo.AudioData.GetView().GetData(), EncodedAudioInfo.AudioData.GetView().Num());
|
||||
CompressedBulkData.Unlock();
|
||||
}
|
||||
|
||||
USoundWave::InitAudioResource(CompressedBulkData);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::IsSeekable() const
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
return PCMBufferInfo.IsValid() && PCMBufferInfo.Get()->PCMData.GetView().Num() > 0 && PCMBufferInfo.Get()->PCMNumOfFrames > 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int32 UImportedSoundWave::OnGeneratePCMAudio(TArray<uint8>& OutAudio, int32 NumSamples)
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
if (!PCMBufferInfo.IsValid())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Ensure there is enough number of frames. Lack of frames means audio playback has finished
|
||||
if (GetNumOfPlayedFrames_Internal() >= PCMBufferInfo->PCMNumOfFrames)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Getting the remaining number of samples if the required number of samples is greater than the total available number
|
||||
if (GetNumOfPlayedFrames_Internal() + (static_cast<uint32>(NumSamples) / static_cast<uint32>(NumChannels)) >= PCMBufferInfo->PCMNumOfFrames)
|
||||
{
|
||||
NumSamples = (PCMBufferInfo->PCMNumOfFrames - GetNumOfPlayedFrames_Internal()) * NumChannels;
|
||||
}
|
||||
|
||||
// Retrieving a part of PCM data
|
||||
float* RetrievedPCMDataPtr = PCMBufferInfo->PCMData.GetView().GetData() + (GetNumOfPlayedFrames_Internal() * NumChannels);
|
||||
const int32 RetrievedPCMDataSize = NumSamples * sizeof(float);
|
||||
|
||||
// Ensure we got a valid PCM data
|
||||
if (RetrievedPCMDataSize <= 0 || !RetrievedPCMDataPtr)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to get PCM audio from imported sound wave since the retrieved PCM data is invalid"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Filling in OutAudio array with the retrieved PCM data
|
||||
OutAudio = TArray<uint8>(reinterpret_cast<uint8*>(RetrievedPCMDataPtr), RetrievedPCMDataSize);
|
||||
|
||||
// Increasing the number of frames played
|
||||
SetNumOfPlayedFrames_Internal(GetNumOfPlayedFrames_Internal() + (NumSamples / NumChannels));
|
||||
|
||||
if (OnGeneratePCMDataNative.IsBound() || OnGeneratePCMData.IsBound())
|
||||
{
|
||||
TArray<float> PCMData(RetrievedPCMDataPtr, NumSamples);
|
||||
AsyncTask(ENamedThreads::GameThread, [this, PCMData = MoveTemp(PCMData)]() mutable
|
||||
{
|
||||
if (OnGeneratePCMDataNative.IsBound())
|
||||
{
|
||||
OnGeneratePCMDataNative.Broadcast(PCMData);
|
||||
}
|
||||
|
||||
if (OnGeneratePCMData.IsBound())
|
||||
{
|
||||
OnGeneratePCMData.Broadcast(PCMData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return NumSamples;
|
||||
}
|
||||
|
||||
void UImportedSoundWave::BeginDestroy()
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Warning, TEXT("Imported sound wave ('%s') data will be cleared because it is being unloaded"), *GetName());
|
||||
|
||||
Super::BeginDestroy();
|
||||
}
|
||||
|
||||
void UImportedSoundWave::Parse(FAudioDevice* AudioDevice, const UPTRINT NodeWaveInstanceHash, FActiveSound& ActiveSound, const FSoundParseParameters& ParseParams, TArray<FWaveInstance*>& WaveInstances)
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
#if UE_VERSION_NEWER_THAN(5, 0, 0)
|
||||
if (ActiveSound.PlaybackTime == 0.f)
|
||||
{
|
||||
RewindPlaybackTime_Internal(ParseParams.StartTime);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Stopping all other active sounds that are using the same sound wave, so that only one sound wave can be played at a time
|
||||
const TArray<FActiveSound*>& ActiveSounds = AudioDevice->GetActiveSounds();
|
||||
for (FActiveSound* ActiveSoundPtr : ActiveSounds)
|
||||
{
|
||||
if (ActiveSoundPtr->GetSound() == this && &ActiveSound != ActiveSoundPtr)
|
||||
{
|
||||
AudioDevice->StopActiveSound(ActiveSoundPtr);
|
||||
}
|
||||
}
|
||||
|
||||
ActiveSound.PlaybackTime = GetPlaybackTime_Internal();
|
||||
|
||||
if (IsPlaybackFinished_Internal())
|
||||
{
|
||||
if (!PlaybackFinishedBroadcast)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Warning, TEXT("Playback of the sound wave '%s' has been completed"), *GetName());
|
||||
|
||||
PlaybackFinishedBroadcast = true;
|
||||
|
||||
if (OnAudioPlaybackFinishedNative.IsBound() || OnAudioPlaybackFinished.IsBound())
|
||||
{
|
||||
AsyncTask(ENamedThreads::GameThread, [this]()
|
||||
{
|
||||
if (OnAudioPlaybackFinishedNative.IsBound())
|
||||
{
|
||||
OnAudioPlaybackFinishedNative.Broadcast();
|
||||
}
|
||||
|
||||
if (OnAudioPlaybackFinished.IsBound())
|
||||
{
|
||||
OnAudioPlaybackFinished.Broadcast();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!bLooping)
|
||||
{
|
||||
if (bStopSoundOnPlaybackFinish)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Playback of the sound wave '%s' has reached the end and will be stopped"), *GetName());
|
||||
AudioDevice->StopActiveSound(&ActiveSound);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("The sound wave '%s' will be looped"), *GetName());
|
||||
ActiveSound.PlaybackTime = 0.f;
|
||||
RewindPlaybackTime_Internal(0.f);
|
||||
}
|
||||
}
|
||||
|
||||
Super::Parse(AudioDevice, NodeWaveInstanceHash, ActiveSound, ParseParams, WaveInstances);
|
||||
}
|
||||
|
||||
void UImportedSoundWave::PopulateAudioDataFromDecodedInfo(FDecodedAudioStruct&& DecodedAudioInfo)
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
const FString DecodedAudioInfoString = DecodedAudioInfo.ToString();
|
||||
|
||||
Duration = DecodedAudioInfo.SoundWaveBasicInfo.Duration;
|
||||
#if UE_VERSION_NEWER_THAN(5, 0, 0)
|
||||
SetImportedSampleRate(0);
|
||||
#endif
|
||||
SetSampleRate(DecodedAudioInfo.SoundWaveBasicInfo.SampleRate);
|
||||
NumChannels = DecodedAudioInfo.SoundWaveBasicInfo.NumOfChannels;
|
||||
|
||||
PCMBufferInfo->PCMData = MoveTemp(DecodedAudioInfo.PCMInfo.PCMData);
|
||||
PCMBufferInfo->PCMNumOfFrames = DecodedAudioInfo.PCMInfo.PCMNumOfFrames;
|
||||
|
||||
if (OnPopulateAudioDataNative.IsBound() || OnPopulateAudioData.IsBound())
|
||||
{
|
||||
TArray<float> PCMData(PCMBufferInfo->PCMData.GetView().GetData(), PCMBufferInfo->PCMData.GetView().Num());
|
||||
AsyncTask(ENamedThreads::GameThread, [this, PCMData = MoveTemp(PCMData)]() mutable
|
||||
{
|
||||
if (OnPopulateAudioDataNative.IsBound())
|
||||
{
|
||||
OnPopulateAudioDataNative.Broadcast(PCMData);
|
||||
}
|
||||
|
||||
if (OnPopulateAudioData.IsBound())
|
||||
{
|
||||
OnPopulateAudioData.Broadcast(PCMData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("The audio data has been populated successfully. Information about audio data:\n%s"), *DecodedAudioInfoString);
|
||||
}
|
||||
|
||||
void UImportedSoundWave::PrepareSoundWaveForMetaSounds(const FOnPrepareSoundWaveForMetaSoundsResult& Result)
|
||||
{
|
||||
PrepareSoundWaveForMetaSounds(FOnPrepareSoundWaveForMetaSoundsResultNative::CreateWeakLambda(this, [Result](bool bSucceeded)
|
||||
{
|
||||
Result.ExecuteIfBound(bSucceeded);
|
||||
}));
|
||||
}
|
||||
|
||||
void UImportedSoundWave::PrepareSoundWaveForMetaSounds(const FOnPrepareSoundWaveForMetaSoundsResultNative& Result)
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [this, Result]()
|
||||
{
|
||||
FGCObjectScopeGuard Guard(this);
|
||||
|
||||
auto ExecuteResult = [Result](bool bSucceeded)
|
||||
{
|
||||
AsyncTask(ENamedThreads::GameThread, [Result, bSucceeded]()
|
||||
{
|
||||
Result.ExecuteIfBound(bSucceeded);
|
||||
});
|
||||
};
|
||||
|
||||
const bool bSucceeded = InitAudioResource(Audio::NAME_OGG);
|
||||
if (bSucceeded)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully prepared the sound wave '%s' for MetaSounds"), *GetName());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to initialize audio resource to prepare the sound wave '%s' for MetaSounds"), *GetName());
|
||||
}
|
||||
|
||||
ExecuteResult(bSucceeded);
|
||||
});
|
||||
#else
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("PrepareSoundWaveForMetaSounds works only for Unreal Engine version >= 5.2 and if explicitly enabled in RuntimeAudioImporter.Build.cs"));
|
||||
Result.ExecuteIfBound(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UImportedSoundWave::ReleaseMemory()
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
UE_LOG(LogRuntimeAudioImporter, Warning, TEXT("Releasing memory for the sound wave '%s'"), *GetName());
|
||||
PCMBufferInfo->PCMData.Empty();
|
||||
PCMBufferInfo->PCMNumOfFrames = 0;
|
||||
}
|
||||
|
||||
void UImportedSoundWave::ReleasePlayedAudioData(const FOnPlayedAudioDataReleaseResult& Result)
|
||||
{
|
||||
ReleasePlayedAudioData(FOnPlayedAudioDataReleaseResultNative::CreateWeakLambda(this, [Result](bool bSucceeded)
|
||||
{
|
||||
Result.ExecuteIfBound(bSucceeded);
|
||||
}));
|
||||
}
|
||||
|
||||
void UImportedSoundWave::ReleasePlayedAudioData(const FOnPlayedAudioDataReleaseResultNative& Result)
|
||||
{
|
||||
AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [this, Result]() mutable
|
||||
{
|
||||
FGCObjectScopeGuard Guard(this);
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
auto ExecuteResult = [Result](bool bSucceeded)
|
||||
{
|
||||
AsyncTask(ENamedThreads::GameThread, [Result, bSucceeded]()
|
||||
{
|
||||
Result.ExecuteIfBound(bSucceeded);
|
||||
});
|
||||
};
|
||||
|
||||
if (GetNumOfPlayedFrames_Internal() == 0)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Warning, TEXT("No audio data will be released because the current playback time is zero"));
|
||||
ExecuteResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const int64 OldNumOfPCMData = PCMBufferInfo->PCMData.GetView().Num();
|
||||
if (GetNumOfPlayedFrames_Internal() >= PCMBufferInfo->PCMNumOfFrames)
|
||||
{
|
||||
ReleaseMemory();
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully released all PCM data (%lld)"), OldNumOfPCMData);
|
||||
ExecuteResult(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const int64 NewPCMDataSize = (PCMBufferInfo->PCMNumOfFrames - GetNumOfPlayedFrames_Internal()) * NumChannels;
|
||||
float* NewPCMDataPtr = static_cast<float*>(FMemory::Malloc(NewPCMDataSize * sizeof(float)));
|
||||
if (!NewPCMDataPtr)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to allocate new memory to free already played audio data"));
|
||||
ExecuteResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// PCM data offset to retrieve remaining data for playback
|
||||
const int64 PCMDataOffset = GetNumOfPlayedFrames_Internal() * NumChannels;
|
||||
|
||||
FMemory::Memcpy(NewPCMDataPtr, PCMBufferInfo->PCMData.GetView().GetData() + PCMDataOffset, NewPCMDataSize * sizeof(float));
|
||||
PCMBufferInfo->PCMData = FRuntimeBulkDataBuffer<float>(NewPCMDataPtr, NewPCMDataSize);
|
||||
|
||||
// Decreasing the amount of PCM frames
|
||||
PCMBufferInfo->PCMNumOfFrames -= GetNumOfPlayedFrames_Internal();
|
||||
|
||||
// Decreasing duration and increasing duration offset
|
||||
{
|
||||
const float DurationOffsetToReduce = GetPlaybackTime_Internal();
|
||||
Duration -= DurationOffsetToReduce;
|
||||
DurationOffset += DurationOffsetToReduce;
|
||||
}
|
||||
|
||||
PlayedNumOfFrames = 0;
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully released %lld number of PCM data"), static_cast<int64>(OldNumOfPCMData - PCMBufferInfo->PCMData.GetView().Num()));
|
||||
ExecuteResult(true);
|
||||
});
|
||||
}
|
||||
|
||||
void UImportedSoundWave::SetLooping(bool bLoop)
|
||||
{
|
||||
bLooping = bLoop;
|
||||
}
|
||||
|
||||
void UImportedSoundWave::SetSubtitles(const TArray<FEditableSubtitleCue>& InSubtitles)
|
||||
{
|
||||
Subtitles.Empty();
|
||||
|
||||
for (const FEditableSubtitleCue& Subtitle : InSubtitles)
|
||||
{
|
||||
FSubtitleCue ConvertedSubtitle;
|
||||
{
|
||||
ConvertedSubtitle.Text = Subtitle.Text;
|
||||
ConvertedSubtitle.Time = Subtitle.Time;
|
||||
}
|
||||
|
||||
Subtitles.Add(ConvertedSubtitle);
|
||||
}
|
||||
}
|
||||
|
||||
void UImportedSoundWave::SetVolume(float InVolume)
|
||||
{
|
||||
Volume = InVolume;
|
||||
}
|
||||
|
||||
void UImportedSoundWave::SetPitch(float InPitch)
|
||||
{
|
||||
Pitch = InPitch;
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::RewindPlaybackTime(float PlaybackTime)
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
return RewindPlaybackTime_Internal(PlaybackTime - GetDurationOffset_Internal());
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::RewindPlaybackTime_Internal(float PlaybackTime)
|
||||
{
|
||||
if (PlaybackTime > Duration)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to rewind playback time for the imported sound wave '%s' by time '%f' because total length is '%f'"), *GetName(), PlaybackTime, Duration);
|
||||
return false;
|
||||
}
|
||||
|
||||
return SetNumOfPlayedFrames_Internal(PlaybackTime * SampleRate);
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::ResampleSoundWave(int32 NewSampleRate)
|
||||
{
|
||||
if (NewSampleRate == GetSampleRate())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Warning, TEXT("Skipping resampling the imported sound wave '%s' because the new sample rate '%d' is the same as the current sample rate '%d'"), *GetName(), NewSampleRate, GetSampleRate());
|
||||
return true;
|
||||
}
|
||||
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
Audio::FAlignedFloatBuffer NewPCMData;
|
||||
Audio::FAlignedFloatBuffer SourcePCMData = Audio::FAlignedFloatBuffer(PCMBufferInfo->PCMData.GetView().GetData(), PCMBufferInfo->PCMData.GetView().Num());
|
||||
|
||||
if (!FRAW_RuntimeCodec::ResampleRAWData(SourcePCMData, GetNumOfChannels(), GetSampleRate(), NewSampleRate, NewPCMData))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to resample the imported sound wave '%s' from sample rate '%d' to sample rate '%d'"), *GetName(), GetSampleRate(), NewSampleRate);
|
||||
return false;
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully resampled the imported sound wave '%s' from sample rate '%d' to sample rate '%d'"), *GetName(), GetSampleRate(), NewSampleRate);
|
||||
SampleRate = NewSampleRate;
|
||||
{
|
||||
PCMBufferInfo->PCMNumOfFrames = NewPCMData.Num() / GetNumOfChannels();
|
||||
PCMBufferInfo->PCMData = FRuntimeBulkDataBuffer<float>(NewPCMData);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::MixSoundWaveChannels(int32 NewNumOfChannels)
|
||||
{
|
||||
if (NewNumOfChannels == GetNumOfChannels())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Warning, TEXT("Skipping mixing the imported sound wave '%s' because the new number of channels '%d' is the same as the current number of channels '%d'"), *GetName(), NewNumOfChannels, GetNumOfChannels());
|
||||
return true;
|
||||
}
|
||||
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
Audio::FAlignedFloatBuffer NewPCMData;
|
||||
Audio::FAlignedFloatBuffer SourcePCMData = Audio::FAlignedFloatBuffer(PCMBufferInfo->PCMData.GetView().GetData(), PCMBufferInfo->PCMData.GetView().Num());
|
||||
|
||||
if (!FRAW_RuntimeCodec::MixChannelsRAWData(SourcePCMData, GetSampleRate(), GetNumOfChannels(), NewNumOfChannels, NewPCMData))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to mix the imported sound wave '%s' from number of channels '%d' to number of channels '%d'"), *GetName(), GetNumOfChannels(), NewNumOfChannels);
|
||||
return false;
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully mixed the imported sound wave '%s' from number of channels '%d' to number of channels '%d'"), *GetName(), GetNumOfChannels(), NewNumOfChannels);
|
||||
NumChannels = NewNumOfChannels;
|
||||
{
|
||||
PCMBufferInfo->PCMNumOfFrames = NewPCMData.Num() / GetNumOfChannels();
|
||||
PCMBufferInfo->PCMData = FRuntimeBulkDataBuffer<float>(NewPCMData);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::SetNumOfPlayedFrames(uint32 NumOfFrames)
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
return SetNumOfPlayedFrames_Internal(NumOfFrames);
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::SetNumOfPlayedFrames_Internal(uint32 NumOfFrames)
|
||||
{
|
||||
if (NumOfFrames < 0 || NumOfFrames > PCMBufferInfo->PCMNumOfFrames)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Cannot change the current frame for the imported sound wave '%s' to frame '%d' because the total number of frames is '%d'"), *GetName(), NumOfFrames, PCMBufferInfo->PCMNumOfFrames);
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayedNumOfFrames = NumOfFrames;
|
||||
|
||||
ResetPlaybackFinish();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 UImportedSoundWave::GetNumOfPlayedFrames() const
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
return GetNumOfPlayedFrames_Internal();
|
||||
}
|
||||
|
||||
uint32 UImportedSoundWave::GetNumOfPlayedFrames_Internal() const
|
||||
{
|
||||
return PlayedNumOfFrames;
|
||||
}
|
||||
|
||||
float UImportedSoundWave::GetPlaybackTime() const
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
return GetPlaybackTime_Internal() + GetDurationOffset_Internal();
|
||||
}
|
||||
|
||||
float UImportedSoundWave::GetPlaybackTime_Internal() const
|
||||
{
|
||||
if (GetNumOfPlayedFrames() == 0 || SampleRate <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<float>(GetNumOfPlayedFrames()) / SampleRate;
|
||||
}
|
||||
|
||||
float UImportedSoundWave::GetDurationConst() const
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
return GetDurationConst_Internal() + GetDurationOffset_Internal();
|
||||
}
|
||||
|
||||
float UImportedSoundWave::GetDurationConst_Internal() const
|
||||
{
|
||||
return Duration;
|
||||
}
|
||||
|
||||
float UImportedSoundWave::GetDuration()
|
||||
#if UE_VERSION_NEWER_THAN(5, 0, 0)
|
||||
const
|
||||
#endif
|
||||
{
|
||||
return GetDurationConst();
|
||||
}
|
||||
|
||||
int32 UImportedSoundWave::GetSampleRate() const
|
||||
{
|
||||
return SampleRate;
|
||||
}
|
||||
|
||||
int32 UImportedSoundWave::GetNumOfChannels() const
|
||||
{
|
||||
return NumChannels;
|
||||
}
|
||||
|
||||
float UImportedSoundWave::GetPlaybackPercentage() const
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
if (GetNumOfPlayedFrames_Internal() == 0 || PCMBufferInfo->PCMNumOfFrames == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<float>(GetNumOfPlayedFrames_Internal()) / PCMBufferInfo->PCMNumOfFrames * 100;
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::IsPlaybackFinished() const
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
return IsPlaybackFinished_Internal();
|
||||
}
|
||||
|
||||
float UImportedSoundWave::GetDurationOffset() const
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
return DurationOffset;
|
||||
}
|
||||
|
||||
float UImportedSoundWave::GetDurationOffset_Internal() const
|
||||
{
|
||||
return DurationOffset;
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::IsPlaybackFinished_Internal() const
|
||||
{
|
||||
// Are there enough frames for future playback from the current ones or not
|
||||
const bool bOutOfFrames = GetNumOfPlayedFrames_Internal() >= PCMBufferInfo->PCMNumOfFrames;
|
||||
|
||||
// Is PCM data valid
|
||||
const bool bValidPCMData = PCMBufferInfo.IsValid();
|
||||
|
||||
return bOutOfFrames && bValidPCMData;
|
||||
}
|
||||
|
||||
bool UImportedSoundWave::GetAudioHeaderInfo(FRuntimeAudioHeaderInfo& HeaderInfo) const
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
if (!PCMBufferInfo.IsValid())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to retrieve audio header information due to an invalid PCM buffer"));
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
HeaderInfo.Duration = GetDurationConst();
|
||||
HeaderInfo.AudioFormat = ERuntimeAudioFormat::Auto;
|
||||
HeaderInfo.SampleRate = GetSampleRate();
|
||||
HeaderInfo.NumOfChannels = NumChannels;
|
||||
HeaderInfo.PCMDataSize = PCMBufferInfo->PCMData.GetView().Num();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UImportedSoundWave::ResetPlaybackFinish()
|
||||
{
|
||||
PlaybackFinishedBroadcast = false;
|
||||
}
|
||||
|
||||
TArray<float> UImportedSoundWave::GetPCMBufferCopy()
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
return TArray<float>(PCMBufferInfo.Get()->PCMData.GetView().GetData(), PCMBufferInfo.Get()->PCMData.GetView().Num());
|
||||
}
|
||||
|
||||
const FPCMStruct& UImportedSoundWave::GetPCMBuffer() const
|
||||
{
|
||||
return *PCMBufferInfo.Get();
|
||||
}
|
@ -0,0 +1,320 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "Sound/StreamingSoundWave.h"
|
||||
#include "RuntimeAudioImporterLibrary.h"
|
||||
#include "Codecs/RAW_RuntimeCodec.h"
|
||||
|
||||
#include "Async/Async.h"
|
||||
#include "UObject/GCObjectScopeGuard.h"
|
||||
#include "SampleBuffer.h"
|
||||
|
||||
UStreamingSoundWave::UStreamingSoundWave(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
PlaybackFinishedBroadcast = true;
|
||||
|
||||
// No need to stop the sound after the end of streaming sound wave playback, assuming the PCM data can be filled after that
|
||||
// (except if this is overridden in SetStopSoundOnPlaybackFinish)
|
||||
bStopSoundOnPlaybackFinish = false;
|
||||
|
||||
// No need to loop streaming sound wave by default
|
||||
bLooping = false;
|
||||
|
||||
// It is necessary to populate the sample rate and the number of channels to make the streaming wave playable even if there is no audio data
|
||||
// (since the audio data may be filled in after the sound wave starts playing)
|
||||
{
|
||||
SetSampleRate(44100);
|
||||
NumChannels = 2;
|
||||
}
|
||||
|
||||
bFilledInitialAudioData = false;
|
||||
NumOfPreAllocatedByteData = 0;
|
||||
}
|
||||
|
||||
void UStreamingSoundWave::PopulateAudioDataFromDecodedInfo(FDecodedAudioStruct&& DecodedAudioInfo)
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
if (!DecodedAudioInfo.IsValid())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to continue populating the audio data because the decoded info is invalid"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the initial audio data if it hasn't already been filled in
|
||||
if (!bFilledInitialAudioData)
|
||||
{
|
||||
SetSampleRate(DecodedAudioInfo.SoundWaveBasicInfo.SampleRate);
|
||||
NumChannels = DecodedAudioInfo.SoundWaveBasicInfo.NumOfChannels;
|
||||
bFilledInitialAudioData = true;
|
||||
}
|
||||
|
||||
// Check if the number of channels and the sampling rate of the sound wave and the input audio data match
|
||||
if (SampleRate != DecodedAudioInfo.SoundWaveBasicInfo.SampleRate || NumChannels != DecodedAudioInfo.SoundWaveBasicInfo.NumOfChannels)
|
||||
{
|
||||
Audio::FAlignedFloatBuffer WaveData(DecodedAudioInfo.PCMInfo.PCMData.GetView().GetData(), DecodedAudioInfo.PCMInfo.PCMData.GetView().Num());
|
||||
|
||||
// Resampling if needed
|
||||
if (SampleRate != DecodedAudioInfo.SoundWaveBasicInfo.SampleRate)
|
||||
{
|
||||
Audio::FAlignedFloatBuffer ResamplerOutputData;
|
||||
if (!FRAW_RuntimeCodec::ResampleRAWData(WaveData, GetNumOfChannels(), GetSampleRate(), DecodedAudioInfo.SoundWaveBasicInfo.SampleRate, ResamplerOutputData))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to resample audio data to the sound wave's sample rate. Resampling failed"));
|
||||
return;
|
||||
}
|
||||
WaveData = MoveTemp(ResamplerOutputData);
|
||||
}
|
||||
|
||||
// Mixing the channels if needed
|
||||
if (NumChannels != DecodedAudioInfo.SoundWaveBasicInfo.NumOfChannels)
|
||||
{
|
||||
Audio::FAlignedFloatBuffer WaveDataTemp;
|
||||
if (!FRAW_RuntimeCodec::MixChannelsRAWData(WaveData, DecodedAudioInfo.SoundWaveBasicInfo.SampleRate, GetNumOfChannels(), DecodedAudioInfo.SoundWaveBasicInfo.NumOfChannels, WaveDataTemp))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to mix audio data to the sound wave's number of channels. Mixing failed"));
|
||||
return;
|
||||
}
|
||||
WaveData = MoveTemp(WaveDataTemp);
|
||||
}
|
||||
|
||||
DecodedAudioInfo.PCMInfo.PCMData = FRuntimeBulkDataBuffer<float>(WaveData);
|
||||
}
|
||||
|
||||
// Do not reallocate the entire PCM buffer if it has free space to fill in
|
||||
if (static_cast<uint64>(NumOfPreAllocatedByteData) >= DecodedAudioInfo.PCMInfo.PCMData.GetView().Num() * sizeof(float))
|
||||
{
|
||||
// This should be changed somehow to work with the new calculations
|
||||
FMemory::Memcpy(reinterpret_cast<uint8*>(PCMBufferInfo->PCMData.GetView().GetData()) + ((PCMBufferInfo->PCMData.GetView().Num() * sizeof(float)) - NumOfPreAllocatedByteData), DecodedAudioInfo.PCMInfo.PCMData.GetView().GetData(), DecodedAudioInfo.PCMInfo.PCMData.GetView().Num() * sizeof(float));
|
||||
NumOfPreAllocatedByteData -= DecodedAudioInfo.PCMInfo.PCMData.GetView().Num() * sizeof(float);
|
||||
NumOfPreAllocatedByteData = NumOfPreAllocatedByteData < 0 ? 0 : NumOfPreAllocatedByteData;
|
||||
}
|
||||
else
|
||||
{
|
||||
const int64 NewPCMDataSize = ((PCMBufferInfo->PCMData.GetView().Num() * sizeof(float)) + (DecodedAudioInfo.PCMInfo.PCMData.GetView().Num() * sizeof(float)) - NumOfPreAllocatedByteData) / sizeof(float);
|
||||
float* NewPCMDataPtr = static_cast<float*>(FMemory::Malloc(NewPCMDataSize * sizeof(float)));
|
||||
|
||||
if (!NewPCMDataPtr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Adding new PCM data at the end
|
||||
{
|
||||
FMemory::Memcpy(NewPCMDataPtr, PCMBufferInfo->PCMData.GetView().GetData(), (PCMBufferInfo->PCMData.GetView().Num() * sizeof(float)) - NumOfPreAllocatedByteData);
|
||||
FMemory::Memcpy(reinterpret_cast<uint8*>(NewPCMDataPtr) + ((PCMBufferInfo->PCMData.GetView().Num() * sizeof(float)) - NumOfPreAllocatedByteData), DecodedAudioInfo.PCMInfo.PCMData.GetView().GetData(), DecodedAudioInfo.PCMInfo.PCMData.GetView().Num() * sizeof(float));
|
||||
}
|
||||
|
||||
PCMBufferInfo->PCMData = FRuntimeBulkDataBuffer<float>(NewPCMDataPtr, NewPCMDataSize);
|
||||
NumOfPreAllocatedByteData = 0;
|
||||
}
|
||||
|
||||
PCMBufferInfo->PCMNumOfFrames += DecodedAudioInfo.PCMInfo.PCMNumOfFrames;
|
||||
Duration += DecodedAudioInfo.SoundWaveBasicInfo.Duration;
|
||||
ResetPlaybackFinish();
|
||||
|
||||
if (OnPopulateAudioDataNative.IsBound() || OnPopulateAudioData.IsBound())
|
||||
{
|
||||
TArray<float> PCMData(DecodedAudioInfo.PCMInfo.PCMData.GetView().GetData(), DecodedAudioInfo.PCMInfo.PCMData.GetView().Num());
|
||||
AsyncTask(ENamedThreads::GameThread, [this, PCMData = MoveTemp(PCMData)]() mutable
|
||||
{
|
||||
if (OnPopulateAudioDataNative.IsBound())
|
||||
{
|
||||
OnPopulateAudioDataNative.Broadcast(PCMData);
|
||||
}
|
||||
|
||||
if (OnPopulateAudioData.IsBound())
|
||||
{
|
||||
OnPopulateAudioData.Broadcast(PCMData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully added audio data to streaming sound wave.\nAdded audio info: %s"), *DecodedAudioInfo.ToString());
|
||||
}
|
||||
|
||||
void UStreamingSoundWave::ReleaseMemory()
|
||||
{
|
||||
Super::ReleaseMemory();
|
||||
NumOfPreAllocatedByteData = 0;
|
||||
}
|
||||
|
||||
void UStreamingSoundWave::ReleasePlayedAudioData(const FOnPlayedAudioDataReleaseResultNative& Result)
|
||||
{
|
||||
FScopeLock Lock(&DataGuard);
|
||||
const int64 NewPCMDataSize = (PCMBufferInfo->PCMNumOfFrames - GetNumOfPlayedFrames_Internal()) * NumChannels;
|
||||
|
||||
if (GetNumOfPlayedFrames_Internal() > 0 && NumOfPreAllocatedByteData > 0 && NewPCMDataSize < PCMBufferInfo->PCMData.GetView().Num())
|
||||
{
|
||||
NumOfPreAllocatedByteData -= (PCMBufferInfo->PCMData.GetView().Num() * sizeof(float)) - (NewPCMDataSize * sizeof(float));
|
||||
NumOfPreAllocatedByteData = NumOfPreAllocatedByteData < 0 ? 0 : NumOfPreAllocatedByteData;
|
||||
}
|
||||
Super::ReleasePlayedAudioData(Result);
|
||||
}
|
||||
|
||||
UStreamingSoundWave* UStreamingSoundWave::CreateStreamingSoundWave()
|
||||
{
|
||||
if (!IsInGameThread())
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to create a sound wave outside of the game thread"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return NewObject<UStreamingSoundWave>();
|
||||
}
|
||||
|
||||
void UStreamingSoundWave::PreAllocateAudioData(int64 NumOfBytesToPreAllocate, const FOnPreAllocateAudioDataResult& Result)
|
||||
{
|
||||
PreAllocateAudioData(NumOfBytesToPreAllocate, FOnPreAllocateAudioDataResultNative::CreateWeakLambda(this, [Result](bool bSucceeded)
|
||||
{
|
||||
Result.ExecuteIfBound(bSucceeded);
|
||||
}));
|
||||
}
|
||||
|
||||
void UStreamingSoundWave::PreAllocateAudioData(int64 NumOfBytesToPreAllocate, const FOnPreAllocateAudioDataResultNative& Result)
|
||||
{
|
||||
AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [this, NumOfBytesToPreAllocate, Result]()
|
||||
{
|
||||
FGCObjectScopeGuard Guard(this);
|
||||
FScopeLock Lock(&DataGuard);
|
||||
|
||||
auto ExecuteResult = [Result](bool bSucceeded)
|
||||
{
|
||||
AsyncTask(ENamedThreads::GameThread, [Result, bSucceeded]()
|
||||
{
|
||||
Result.ExecuteIfBound(bSucceeded);
|
||||
});
|
||||
};
|
||||
|
||||
if (PCMBufferInfo->PCMData.GetView().Num() > 0 || NumOfPreAllocatedByteData > 0)
|
||||
{
|
||||
ensureMsgf(false, TEXT("Pre-allocation of PCM data can only be applied if the PCM data has not yet been allocated"));
|
||||
ExecuteResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
float* NewPCMDataPtr = static_cast<float*>(FMemory::Malloc(NumOfBytesToPreAllocate));
|
||||
if (!NewPCMDataPtr)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to allocate memory to pre-allocate streaming sound wave audio data"));
|
||||
ExecuteResult(false);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
NumOfPreAllocatedByteData = NumOfBytesToPreAllocate;
|
||||
PCMBufferInfo->PCMData = FRuntimeBulkDataBuffer<float>(NewPCMDataPtr, NumOfBytesToPreAllocate / sizeof(float));
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Successfully pre-allocated '%lld' number of bytes"), NumOfBytesToPreAllocate);
|
||||
ExecuteResult(true);
|
||||
});
|
||||
}
|
||||
|
||||
void UStreamingSoundWave::AppendAudioDataFromEncoded(TArray<uint8> AudioData, ERuntimeAudioFormat AudioFormat)
|
||||
{
|
||||
AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [this, AudioData = MoveTemp(AudioData), AudioFormat]()
|
||||
{
|
||||
FEncodedAudioStruct EncodedAudioInfo(AudioData, AudioFormat);
|
||||
FDecodedAudioStruct DecodedAudioInfo;
|
||||
if (!URuntimeAudioImporterLibrary::DecodeAudioData(MoveTemp(EncodedAudioInfo), DecodedAudioInfo))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to decode audio data to populate streaming sound wave audio data"));
|
||||
return;
|
||||
}
|
||||
|
||||
PopulateAudioDataFromDecodedInfo(MoveTemp(DecodedAudioInfo));
|
||||
});
|
||||
}
|
||||
|
||||
void UStreamingSoundWave::AppendAudioDataFromRAW(TArray<uint8> RAWData, ERuntimeRAWAudioFormat RAWFormat, int32 InSampleRate, int32 NumOfChannels)
|
||||
{
|
||||
AsyncTask(ENamedThreads::AnyBackgroundHiPriTask, [this, RAWData = MoveTemp(RAWData), RAWFormat, InSampleRate, NumOfChannels]() mutable
|
||||
{
|
||||
uint8* ByteDataPtr = RAWData.GetData();
|
||||
const int64 ByteDataSize = RAWData.Num();
|
||||
|
||||
float* Float32DataPtr = nullptr;
|
||||
int64 NumOfSamples = 0;
|
||||
|
||||
// Transcoding RAW data to 32-bit float data
|
||||
{
|
||||
switch (RAWFormat)
|
||||
{
|
||||
case ERuntimeRAWAudioFormat::Int8:
|
||||
{
|
||||
NumOfSamples = ByteDataSize / sizeof(int8);
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<int8, float>(reinterpret_cast<int8*>(ByteDataPtr), NumOfSamples, Float32DataPtr);
|
||||
break;
|
||||
}
|
||||
case ERuntimeRAWAudioFormat::UInt8:
|
||||
{
|
||||
NumOfSamples = ByteDataSize / sizeof(uint8);
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<uint8, float>(ByteDataPtr, NumOfSamples, Float32DataPtr);
|
||||
break;
|
||||
}
|
||||
case ERuntimeRAWAudioFormat::Int16:
|
||||
{
|
||||
NumOfSamples = ByteDataSize / sizeof(int16);
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<int16, float>(reinterpret_cast<int16*>(ByteDataPtr), NumOfSamples, Float32DataPtr);
|
||||
break;
|
||||
}
|
||||
case ERuntimeRAWAudioFormat::UInt16:
|
||||
{
|
||||
NumOfSamples = ByteDataSize / sizeof(uint16);
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<uint16, float>(reinterpret_cast<uint16*>(ByteDataPtr), NumOfSamples, Float32DataPtr);
|
||||
break;
|
||||
}
|
||||
case ERuntimeRAWAudioFormat::UInt32:
|
||||
{
|
||||
NumOfSamples = ByteDataSize / sizeof(uint32);
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<uint32, float>(reinterpret_cast<uint32*>(ByteDataPtr), NumOfSamples, Float32DataPtr);
|
||||
break;
|
||||
}
|
||||
case ERuntimeRAWAudioFormat::Int32:
|
||||
{
|
||||
NumOfSamples = ByteDataSize / sizeof(int32);
|
||||
FRAW_RuntimeCodec::TranscodeRAWData<int32, float>(reinterpret_cast<int32*>(ByteDataPtr), NumOfSamples, Float32DataPtr);
|
||||
break;
|
||||
}
|
||||
case ERuntimeRAWAudioFormat::Float32:
|
||||
{
|
||||
NumOfSamples = ByteDataSize / sizeof(float);
|
||||
Float32DataPtr = static_cast<float*>(FMemory::Memcpy(FMemory::Malloc(ByteDataSize), ByteDataPtr, ByteDataSize));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Float32DataPtr || NumOfSamples <= 0)
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Failed to transcode RAW data to decoded audio info"))
|
||||
return;
|
||||
}
|
||||
|
||||
FDecodedAudioStruct DecodedAudioInfo;
|
||||
{
|
||||
FPCMStruct PCMInfo;
|
||||
{
|
||||
PCMInfo.PCMData = FRuntimeBulkDataBuffer<float>(Float32DataPtr, NumOfSamples);
|
||||
PCMInfo.PCMNumOfFrames = NumOfSamples / NumOfChannels;
|
||||
}
|
||||
DecodedAudioInfo.PCMInfo = MoveTemp(PCMInfo);
|
||||
|
||||
FSoundWaveBasicStruct SoundWaveBasicInfo;
|
||||
{
|
||||
SoundWaveBasicInfo.NumOfChannels = NumOfChannels;
|
||||
SoundWaveBasicInfo.SampleRate = InSampleRate;
|
||||
SoundWaveBasicInfo.Duration = static_cast<float>(DecodedAudioInfo.PCMInfo.PCMNumOfFrames) / InSampleRate;
|
||||
}
|
||||
DecodedAudioInfo.SoundWaveBasicInfo = MoveTemp(SoundWaveBasicInfo);
|
||||
}
|
||||
|
||||
PopulateAudioDataFromDecodedInfo(MoveTemp(DecodedAudioInfo));
|
||||
});
|
||||
}
|
||||
|
||||
void UStreamingSoundWave::SetStopSoundOnPlaybackFinish(bool bStop)
|
||||
{
|
||||
bStopSoundOnPlaybackFinish = bStop;
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "BaseRuntimeCodec.h"
|
||||
|
||||
class RUNTIMEAUDIOIMPORTER_API FBINK_RuntimeCodec : public FBaseRuntimeCodec
|
||||
{
|
||||
public:
|
||||
//~ Begin FBaseRuntimeCodec Interface
|
||||
virtual bool CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData) override;
|
||||
virtual bool GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo) override;
|
||||
virtual bool Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality) override;
|
||||
virtual bool Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData) override;
|
||||
virtual ERuntimeAudioFormat GetAudioFormat() const override { return ERuntimeAudioFormat::Bink; }
|
||||
//~ End FBaseRuntimeCodec Interface
|
||||
};
|
@ -0,0 +1,63 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "RuntimeAudioImporterTypes.h"
|
||||
|
||||
// TODO: Make FBaseRuntimeCodec an abstract class (currently not possible due to TUniquePtr requiring a non-abstract base class)
|
||||
|
||||
/**
|
||||
* Base runtime codec
|
||||
*/
|
||||
class RUNTIMEAUDIOIMPORTER_API FBaseRuntimeCodec
|
||||
{
|
||||
public:
|
||||
FBaseRuntimeCodec() = default;
|
||||
virtual ~FBaseRuntimeCodec() = default;
|
||||
|
||||
/**
|
||||
* Check if the given audio data appears to be valid
|
||||
*/
|
||||
virtual bool CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData)
|
||||
{
|
||||
ensureMsgf(false, TEXT("CheckAudioFormat cannot be called from base runtime codec"));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve audio header information from an encoded source
|
||||
*/
|
||||
virtual bool GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo)
|
||||
{
|
||||
ensureMsgf(false, TEXT("GetHeaderInfo cannot be called from base runtime codec"));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode uncompressed PCM data into a compressed format
|
||||
*/
|
||||
virtual bool Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality)
|
||||
{
|
||||
ensureMsgf(false, TEXT("Encode cannot be called from base runtime codec"));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode compressed audio data into PCM format
|
||||
*/
|
||||
virtual bool Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData)
|
||||
{
|
||||
ensureMsgf(false, TEXT("Decode cannot be called from base runtime codec"));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the format applicable to this codec
|
||||
*/
|
||||
virtual ERuntimeAudioFormat GetAudioFormat() const
|
||||
{
|
||||
ensureMsgf(false, TEXT("GetAudioFormat cannot be called from base runtime codec"));
|
||||
return ERuntimeAudioFormat::Invalid;
|
||||
}
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "BaseRuntimeCodec.h"
|
||||
|
||||
class RUNTIMEAUDIOIMPORTER_API FFLAC_RuntimeCodec : public FBaseRuntimeCodec
|
||||
{
|
||||
public:
|
||||
//~ Begin FBaseRuntimeCodec Interface
|
||||
virtual bool CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData) override;
|
||||
virtual bool GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo) override;
|
||||
virtual bool Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality) override;
|
||||
virtual bool Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData) override;
|
||||
virtual ERuntimeAudioFormat GetAudioFormat() const override { return ERuntimeAudioFormat::Flac; }
|
||||
//~ End FBaseRuntimeCodec Interface
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "BaseRuntimeCodec.h"
|
||||
|
||||
class RUNTIMEAUDIOIMPORTER_API FMP3_RuntimeCodec : public FBaseRuntimeCodec
|
||||
{
|
||||
public:
|
||||
//~ Begin FBaseRuntimeCodec Interface
|
||||
virtual bool CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData) override;
|
||||
virtual bool GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo) override;
|
||||
virtual bool Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality) override;
|
||||
virtual bool Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData) override;
|
||||
virtual ERuntimeAudioFormat GetAudioFormat() const override { return ERuntimeAudioFormat::Mp3; }
|
||||
//~ End FBaseRuntimeCodec Interface
|
||||
};
|
@ -0,0 +1,168 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Math/UnrealMathUtility.h"
|
||||
#include "HAL/UnrealMemory.h"
|
||||
#include "RuntimeAudioImporterDefines.h"
|
||||
#include "SampleBuffer.h"
|
||||
#include "AudioResampler.h"
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
|
||||
class RUNTIMEAUDIOIMPORTER_API FRAW_RuntimeCodec
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Getting the minimum and maximum values of the specified RAW format
|
||||
*
|
||||
* @note Key - Minimum, Value - Maximum
|
||||
*/
|
||||
template <typename IntegralType>
|
||||
static TTuple<long long, long long> GetRawMinAndMaxValues()
|
||||
{
|
||||
// Signed 8-bit integer
|
||||
if (std::is_same<IntegralType, int8>::value)
|
||||
{
|
||||
return TTuple<long long, long long>(std::numeric_limits<int8>::min(), std::numeric_limits<int8>::max());
|
||||
}
|
||||
|
||||
// Unsigned 8-bit integer
|
||||
if (std::is_same<IntegralType, uint8>::value)
|
||||
{
|
||||
return TTuple<long long, long long>(std::numeric_limits<uint8>::min(), std::numeric_limits<uint8>::max());
|
||||
}
|
||||
|
||||
// Signed 16-bit integer
|
||||
if (std::is_same<IntegralType, int16>::value)
|
||||
{
|
||||
return TTuple<long long, long long>(std::numeric_limits<int16>::min(), std::numeric_limits<int16>::max());
|
||||
}
|
||||
|
||||
// Unsigned 16-bit integer
|
||||
if (std::is_same<IntegralType, uint16>::value)
|
||||
{
|
||||
return TTuple<long long, long long>(std::numeric_limits<uint16>::min(), std::numeric_limits<uint16>::max());
|
||||
}
|
||||
|
||||
// Signed 32-bit integer
|
||||
if (std::is_same<IntegralType, int32>::value)
|
||||
{
|
||||
return TTuple<long long, long long>(std::numeric_limits<int32>::min(), std::numeric_limits<int32>::max());
|
||||
}
|
||||
|
||||
// Unsigned 32-bit integer
|
||||
if (std::is_same<IntegralType, uint32>::value)
|
||||
{
|
||||
return TTuple<long long, long long>(std::numeric_limits<uint32>::min(), std::numeric_limits<uint32>::max());
|
||||
}
|
||||
|
||||
// Floating point 32-bit
|
||||
if (std::is_same<IntegralType, float>::value)
|
||||
{
|
||||
return TTuple<long long, long long>(-1, 1);
|
||||
}
|
||||
|
||||
ensureMsgf(false, TEXT("Unsupported RAW format"));
|
||||
return TTuple<long long, long long>(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transcoding one RAW Data format to another
|
||||
*
|
||||
* @param RAWData_From RAW data for transcoding
|
||||
* @param RAWData_To Transcoded RAW data with the specified format
|
||||
*/
|
||||
template <typename IntegralTypeFrom, typename IntegralTypeTo>
|
||||
static void TranscodeRAWData(const TArray64<uint8>& RAWData_From, TArray64<uint8>& RAWData_To)
|
||||
{
|
||||
const IntegralTypeFrom* DataFrom = reinterpret_cast<const IntegralTypeFrom*>(RAWData_From.GetData());
|
||||
const int64 RawDataSize = RAWData_From.Num() / sizeof(IntegralTypeFrom);
|
||||
|
||||
IntegralTypeTo* DataTo = nullptr;
|
||||
TranscodeRAWData<IntegralTypeFrom, IntegralTypeTo>(DataFrom, RawDataSize, DataTo);
|
||||
|
||||
RAWData_To = TArray64<uint8>(reinterpret_cast<uint8*>(DataTo), RawDataSize * sizeof(IntegralTypeTo));
|
||||
FMemory::Free(DataTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transcoding one RAW Data format to another
|
||||
*
|
||||
* @param RAWDataFrom Pointer to memory location of the RAW data for transcoding
|
||||
* @param NumOfSamples Number of samples in the RAW data
|
||||
* @param RAWDataTo Pointer to memory location of the transcoded RAW data with the specified format. The number of samples is RAWDataSize
|
||||
*/
|
||||
template <typename IntegralTypeFrom, typename IntegralTypeTo>
|
||||
static void TranscodeRAWData(const IntegralTypeFrom* RAWDataFrom, int64 NumOfSamples, IntegralTypeTo*& RAWDataTo)
|
||||
{
|
||||
/** Creating an empty PCM buffer */
|
||||
RAWDataTo = static_cast<IntegralTypeTo*>(FMemory::Malloc(NumOfSamples * sizeof(IntegralTypeTo)));
|
||||
|
||||
const TTuple<long long, long long> MinAndMaxValuesFrom{GetRawMinAndMaxValues<IntegralTypeFrom>()};
|
||||
const TTuple<long long, long long> MinAndMaxValuesTo{GetRawMinAndMaxValues<IntegralTypeTo>()};
|
||||
|
||||
/** Iterating through the RAW Data to transcode values using a divisor */
|
||||
for (int64 SampleIndex = 0; SampleIndex < NumOfSamples; ++SampleIndex)
|
||||
{
|
||||
RAWDataTo[SampleIndex] = static_cast<IntegralTypeTo>(FMath::GetMappedRangeValueClamped(FVector2D(MinAndMaxValuesFrom.Key, MinAndMaxValuesFrom.Value), FVector2D(MinAndMaxValuesTo.Key, MinAndMaxValuesTo.Value), RAWDataFrom[SampleIndex]));
|
||||
}
|
||||
|
||||
UE_LOG(LogRuntimeAudioImporter, Log, TEXT("Transcoding RAW data of size '%llu' (min: %lld, max: %lld) to size '%llu' (min: %lld, max: %lld)"),
|
||||
static_cast<uint64>(sizeof(IntegralTypeFrom)), MinAndMaxValuesFrom.Key, MinAndMaxValuesFrom.Value, static_cast<uint64>(sizeof(IntegralTypeTo)), MinAndMaxValuesTo.Key, MinAndMaxValuesTo.Value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resampling RAW Data to a different sample rate
|
||||
*
|
||||
* @param RAWData RAW data for resampling
|
||||
* @param NumOfChannels Number of channels in the RAW data
|
||||
* @param SourceSampleRate Source sample rate of the RAW data
|
||||
* @param DestinationSampleRate Destination sample rate of the RAW data
|
||||
* @param ResampledRAWData Resampled RAW data
|
||||
* @return True if the RAW data was successfully resampled
|
||||
*/
|
||||
static bool ResampleRAWData(Audio::FAlignedFloatBuffer& RAWData, int32 NumOfChannels, int32 SourceSampleRate, int32 DestinationSampleRate, Audio::FAlignedFloatBuffer& ResampledRAWData)
|
||||
{
|
||||
const Audio::FResamplingParameters ResampleParameters = {
|
||||
Audio::EResamplingMethod::BestSinc,
|
||||
NumOfChannels,
|
||||
static_cast<float>(SourceSampleRate),
|
||||
static_cast<float>(DestinationSampleRate),
|
||||
RAWData
|
||||
};
|
||||
|
||||
ResampledRAWData.AddUninitialized(Audio::GetOutputBufferSize(ResampleParameters));
|
||||
Audio::FResamplerResults ResampleResults;
|
||||
ResampleResults.OutBuffer = &ResampledRAWData;
|
||||
|
||||
if (!Audio::Resample(ResampleParameters, ResampleResults))
|
||||
{
|
||||
UE_LOG(LogRuntimeAudioImporter, Error, TEXT("Unable to resample audio data from %d to %d"), SourceSampleRate, DestinationSampleRate);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixing RAW Data to a different number of channels
|
||||
*
|
||||
* @param RAWData RAW data for mixing
|
||||
* @param SampleRate Sample rate of the RAW data
|
||||
* @param SourceNumOfChannels Source number of channels in the RAW data
|
||||
* @param DestinationNumOfChannels Destination number of channels in the RAW data
|
||||
* @param RemixedRAWData Remixed RAW data
|
||||
* @return True if the RAW data was successfully mixed
|
||||
*/
|
||||
static bool MixChannelsRAWData(Audio::FAlignedFloatBuffer& RAWData, int32 SampleRate, int32 SourceNumOfChannels, int32 DestinationNumOfChannels, Audio::FAlignedFloatBuffer& RemixedRAWData)
|
||||
{
|
||||
Audio::TSampleBuffer<float> PCMSampleBuffer(RAWData, SourceNumOfChannels, SampleRate);
|
||||
{
|
||||
PCMSampleBuffer.MixBufferToChannels(DestinationNumOfChannels);
|
||||
}
|
||||
RemixedRAWData = Audio::FAlignedFloatBuffer(PCMSampleBuffer.GetData(), PCMSampleBuffer.GetNumSamples());
|
||||
return true;
|
||||
}
|
||||
};
|
@ -0,0 +1,39 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BaseRuntimeCodec.h"
|
||||
|
||||
/**
|
||||
* A factory for constructing the codecs used for encoding and decoding audio data
|
||||
*/
|
||||
class RUNTIMEAUDIOIMPORTER_API FRuntimeCodecFactory
|
||||
{
|
||||
public:
|
||||
FRuntimeCodecFactory() = default;
|
||||
virtual ~FRuntimeCodecFactory() = default;
|
||||
|
||||
/**
|
||||
* Get the codec based on the file path extension
|
||||
*
|
||||
* @param FilePath The file path from which to get the codec
|
||||
* @return The detected codec, or a nullptr if it could not be detected
|
||||
*/
|
||||
virtual TUniquePtr<FBaseRuntimeCodec> GetCodec(const FString& FilePath);
|
||||
|
||||
/**
|
||||
* Get the codec based on the audio format
|
||||
*
|
||||
* @param AudioFormat The format from which to get the codec
|
||||
* @return The detected codec, or a nullptr if it could not be detected
|
||||
*/
|
||||
virtual TUniquePtr<FBaseRuntimeCodec> GetCodec(ERuntimeAudioFormat AudioFormat);
|
||||
|
||||
/**
|
||||
* Get the codec based on the audio data (slower, but more reliable)
|
||||
*
|
||||
* @param AudioData The audio data from which to get the codec
|
||||
* @return The detected codec, or a nullptr if it could not be detected
|
||||
*/
|
||||
virtual TUniquePtr<FBaseRuntimeCodec> GetCodec(const FRuntimeBulkDataBuffer<uint8>& AudioData);
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "BaseRuntimeCodec.h"
|
||||
|
||||
class RUNTIMEAUDIOIMPORTER_API FVORBIS_RuntimeCodec : public FBaseRuntimeCodec
|
||||
{
|
||||
public:
|
||||
//~ Begin FBaseRuntimeCodec Interface
|
||||
virtual bool CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData) override;
|
||||
virtual bool GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo) override;
|
||||
virtual bool Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality) override;
|
||||
virtual bool Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData) override;
|
||||
virtual ERuntimeAudioFormat GetAudioFormat() const override { return ERuntimeAudioFormat::OggVorbis; }
|
||||
//~ End FBaseRuntimeCodec Interface
|
||||
};
|
@ -0,0 +1,18 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "BaseRuntimeCodec.h"
|
||||
|
||||
class RUNTIMEAUDIOIMPORTER_API FWAV_RuntimeCodec : public FBaseRuntimeCodec
|
||||
{
|
||||
public:
|
||||
//~ Begin FBaseRuntimeCodec Interface
|
||||
virtual bool CheckAudioFormat(const FRuntimeBulkDataBuffer<uint8>& AudioData) override;
|
||||
virtual bool GetHeaderInfo(FEncodedAudioStruct EncodedData, FRuntimeAudioHeaderInfo& HeaderInfo) override;
|
||||
virtual bool Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality) override;
|
||||
virtual bool Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData) override;
|
||||
virtual ERuntimeAudioFormat GetAudioFormat() const override { return ERuntimeAudioFormat::Wav; }
|
||||
//~ End FBaseRuntimeCodec Interface
|
||||
};
|
@ -0,0 +1,53 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
#include "MetasoundDataReference.h"
|
||||
#include "MetasoundDataTypeRegistrationMacro.h"
|
||||
#include "IAudioProxyInitializer.h"
|
||||
#include "Sound/SoundWave.h"
|
||||
|
||||
namespace RuntimeAudioImporter
|
||||
{
|
||||
extern const FString RUNTIMEAUDIOIMPORTER_API PluginAuthor;
|
||||
extern const FText RUNTIMEAUDIOIMPORTER_API PluginNodeMissingPrompt;
|
||||
|
||||
/**
|
||||
* FImportedWave is an alternative to FWaveAsset to hold proxy data obtained from UImportedSoundWave
|
||||
*/
|
||||
class RUNTIMEAUDIOIMPORTER_API FImportedWave
|
||||
{
|
||||
FSoundWaveProxyPtr SoundWaveProxy;
|
||||
|
||||
public:
|
||||
FImportedWave() = default;
|
||||
FImportedWave(const FImportedWave&) = default;
|
||||
FImportedWave& operator=(const FImportedWave& Other) = default;
|
||||
|
||||
FImportedWave(const TUniquePtr<Audio::IProxyData>& InInitData);
|
||||
|
||||
bool IsSoundWaveValid() const
|
||||
{
|
||||
return SoundWaveProxy.IsValid();
|
||||
}
|
||||
|
||||
const FSoundWaveProxyPtr& GetSoundWaveProxy() const
|
||||
{
|
||||
return SoundWaveProxy;
|
||||
}
|
||||
|
||||
const FSoundWaveProxy* operator->() const
|
||||
{
|
||||
return SoundWaveProxy.Get();
|
||||
}
|
||||
|
||||
FSoundWaveProxy* operator->()
|
||||
{
|
||||
return SoundWaveProxy.Get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
DECLARE_METASOUND_DATA_REFERENCE_TYPES(RuntimeAudioImporter::FImportedWave, RUNTIMEAUDIOIMPORTER_API, FImportedWaveTypeInfo, FImportedWaveReadRef, FImportedWaveWriteRef)
|
||||
#endif
|
@ -0,0 +1,42 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "RuntimeAudioImporterTypes.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 = "Pre Imported Sound Asset")
|
||||
class RUNTIMEAUDIOIMPORTER_API UPreImportedSoundAsset : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPreImportedSoundAsset();
|
||||
|
||||
/** Audio data array */
|
||||
UPROPERTY()
|
||||
TArray<uint8> AudioDataArray;
|
||||
|
||||
/** Audio data format */
|
||||
UPROPERTY(Category = "Info", VisibleAnywhere, Meta = (DisplayName = "Audio format"))
|
||||
ERuntimeAudioFormat AudioFormat;
|
||||
|
||||
/** Information about the basic details of an audio file. Used only for convenience in the editor */
|
||||
#if WITH_EDITORONLY_DATA
|
||||
UPROPERTY(Category = "File Path", 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
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FRuntimeAudioImporterModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
@ -0,0 +1,11 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
#include "Logging/LogCategory.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
#include "Logging/LogVerbosity.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogRuntimeAudioImporter, Log, All);
|
@ -0,0 +1,483 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Sound/ImportedSoundWave.h"
|
||||
#include "RuntimeAudioImporterTypes.h"
|
||||
#include "RuntimeAudioImporterLibrary.generated.h"
|
||||
|
||||
class UPreImportedSoundAsset;
|
||||
class URuntimeAudioImporterLibrary;
|
||||
|
||||
/** Static delegate broadcasting the audio importer progress */
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnAudioImporterProgressNative, int32);
|
||||
|
||||
/** Dynamic delegate broadcasting the audio importer progress */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAudioImporterProgress, int32, Percentage);
|
||||
|
||||
/** Static delegate broadcasting the audio importer result */
|
||||
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnAudioImporterResultNative, URuntimeAudioImporterLibrary*, UImportedSoundWave*, ERuntimeImportStatus);
|
||||
|
||||
/** Dynamic delegate broadcasting the audio importer result */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnAudioImporterResult, URuntimeAudioImporterLibrary*, Importer, UImportedSoundWave*, ImportedSoundWave, ERuntimeImportStatus, Status);
|
||||
|
||||
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnAudioImporterResultNoDynamic, URuntimeAudioImporterLibrary*, UImportedSoundWave*, ERuntimeImportStatus);
|
||||
|
||||
/** Static delegate broadcasting the result of the conversion from SoundWave to ImportedSoundWave */
|
||||
DECLARE_DELEGATE_TwoParams(FOnRegularToAudioImporterSoundWaveConvertResultNative, bool, UImportedSoundWave*);
|
||||
|
||||
/** Dynamic delegate broadcasting the result of the conversion from SoundWave to ImportedSoundWave */
|
||||
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnRegularToAudioImporterSoundWaveConvertResult, bool, bSucceeded, UImportedSoundWave*, ImportedSoundWave);
|
||||
|
||||
|
||||
/** Static delegate broadcasting the result of the audio export to buffer */
|
||||
DECLARE_DELEGATE_TwoParams(FOnAudioExportToBufferResultNative, bool, const TArray64<uint8>&);
|
||||
|
||||
/** Dynamic delegate broadcasting the result of the audio export to buffer */
|
||||
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnAudioExportToBufferResult, bool, bSucceeded, const TArray<uint8>&, AudioData);
|
||||
|
||||
/** Static delegate broadcasting the result of the audio export to file */
|
||||
DECLARE_DELEGATE_OneParam(FOnAudioExportToFileResultNative, bool);
|
||||
|
||||
/** Dynamic delegate broadcasting the result of the audio export to file */
|
||||
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnAudioExportToFileResult, bool, bSucceeded);
|
||||
|
||||
|
||||
/** Static delegate broadcasting the result of the RAW data transcoded from buffer */
|
||||
DECLARE_DELEGATE_TwoParams(FOnRAWDataTranscodeFromBufferResultNative, bool, const TArray64<uint8>&);
|
||||
|
||||
/** Dynamic delegate broadcasting the result of the RAW data transcoded from buffer */
|
||||
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnRAWDataTranscodeFromBufferResult, bool, bSucceeded, const TArray<uint8>&, RAWData);
|
||||
|
||||
|
||||
/** Static delegate broadcasting the result of the RAW data transcoded from file */
|
||||
DECLARE_DELEGATE_OneParam(FOnRAWDataTranscodeFromFileResultNative, bool);
|
||||
|
||||
/** Dynamic delegate broadcasting the result of the RAW data transcoded from file */
|
||||
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnRAWDataTranscodeFromFileResult, bool, bSucceeded);
|
||||
|
||||
|
||||
/** Static delegate broadcasting the result of retrieving audio header info */
|
||||
DECLARE_DELEGATE_TwoParams(FOnGetAudioHeaderInfoResultNative, bool, const FRuntimeAudioHeaderInfo&);
|
||||
|
||||
/** Dynamic delegate broadcasting the result of retrieving audio header info */
|
||||
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnGetAudioHeaderInfoResult, bool, bSucceeded, const FRuntimeAudioHeaderInfo&, HeaderInfo);
|
||||
|
||||
|
||||
/** Dynamic delegate broadcasting the result of scanning directory for audio files */
|
||||
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnScanDirectoryForAudioFilesResult, bool, bSucceeded, const TArray<FString>&, AudioFilePaths);
|
||||
|
||||
/** Static delegate broadcasting the result of scanning directory for audio files */
|
||||
DECLARE_DELEGATE_TwoParams(FOnScanDirectoryForAudioFilesResultNative, bool, const TArray<FString>&);
|
||||
|
||||
|
||||
/**
|
||||
* Runtime Audio Importer library
|
||||
* Various functions related to working with audio data, including importing audio files, manually encoding and decoding audio data, and more
|
||||
*/
|
||||
UCLASS(BlueprintType, Category = "Runtime Audio Importer")
|
||||
class RUNTIMEAUDIOIMPORTER_API URuntimeAudioImporterLibrary : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Bind to know when audio import is on progress. Suitable for use in C++ */
|
||||
FOnAudioImporterProgressNative OnProgressNative;
|
||||
|
||||
/** Bind to know when audio import is on progress */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Runtime Audio Importer|Delegates")
|
||||
FOnAudioImporterProgress OnProgress;
|
||||
|
||||
/** Bind to know when audio import is complete (even if it fails). Suitable for use in C++ */
|
||||
FOnAudioImporterResultNative OnResultNative;
|
||||
|
||||
/** Bind to know when audio import is complete (even if it fails) */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Runtime Audio Importer|Delegates")
|
||||
FOnAudioImporterResult OnResult;
|
||||
|
||||
FOnAudioImporterResultNoDynamic OnResultNoDynamic;
|
||||
|
||||
/**
|
||||
* Tries to retrieve audio data from a given regular sound wave
|
||||
*
|
||||
* @param SoundWave The sound wave from which to obtain audio data
|
||||
* @param OutDecodedAudioInfo The decoded audio information. Populated only if the function returns true
|
||||
* @return True if the audio data was successfully retrieved
|
||||
*/
|
||||
static bool TryToRetrieveSoundWaveData(USoundWave* SoundWave, FDecodedAudioStruct& OutDecodedAudioInfo);
|
||||
|
||||
/**
|
||||
* Instantiate a RuntimeAudioImporter object
|
||||
*
|
||||
* @return The RuntimeAudioImporter object. Bind to it's OnProgress and OnResult delegates
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (Keywords = "Create, Audio, Runtime, MP3, FLAC, WAV, OGG, Vorbis"), Category = "Runtime Audio Importer")
|
||||
static URuntimeAudioImporterLibrary* CreateRuntimeAudioImporter();
|
||||
|
||||
/**
|
||||
* Import audio from a file
|
||||
*
|
||||
* @param FilePath Path to the audio file to import
|
||||
* @param AudioFormat Audio format
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV, OGG, Vorbis"), Category = "Runtime Audio Importer|Import")
|
||||
void ImportAudioFromFile(const FString& FilePath, ERuntimeAudioFormat AudioFormat);
|
||||
|
||||
/**
|
||||
* Import audio from a pre-imported sound asset
|
||||
*
|
||||
* @param PreImportedSoundAsset PreImportedSoundAsset object reference
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV, OGG, Vorbis, BINK"), Category = "Runtime Audio Importer|Import")
|
||||
void ImportAudioFromPreImportedSound(UPreImportedSoundAsset* PreImportedSoundAsset);
|
||||
|
||||
/**
|
||||
* Import audio from a buffer
|
||||
*
|
||||
* @param AudioData Audio data array
|
||||
* @param AudioFormat Audio format
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV, OGG, Vorbis, BINK"), Category = "Runtime Audio Importer|Import")
|
||||
void ImportAudioFromBuffer(TArray<uint8> AudioData, ERuntimeAudioFormat AudioFormat);
|
||||
|
||||
/**
|
||||
* Import audio from a buffer. Suitable for use with 64-bit data size
|
||||
*
|
||||
* @param AudioData Audio data array
|
||||
* @param AudioFormat Audio format
|
||||
*/
|
||||
void ImportAudioFromBuffer(TArray64<uint8> AudioData, ERuntimeAudioFormat AudioFormat);
|
||||
|
||||
/**
|
||||
* Import audio from a RAW file. The audio data must not have headers and must be uncompressed
|
||||
*
|
||||
* @param FilePath Path to the audio file to import
|
||||
* @param RAWFormat RAW audio format
|
||||
* @param SampleRate The number of samples per second
|
||||
* @param NumOfChannels The number of channels (1 for mono, 2 for stereo, etc)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Import Audio From RAW File", Keywords = "PCM, RAW"), Category = "Runtime Audio Importer|Import")
|
||||
void ImportAudioFromRAWFile(const FString& FilePath, UPARAM(DisplayName = "RAW Format") ERuntimeRAWAudioFormat RAWFormat, int32 SampleRate = 44100, int32 NumOfChannels = 1);
|
||||
|
||||
/**
|
||||
* Import audio from a RAW buffer. The audio data must not have headers and must be uncompressed
|
||||
*
|
||||
* @param RAWBuffer RAW audio buffer
|
||||
* @param RAWFormat RAW audio format
|
||||
* @param SampleRate The number of samples per second
|
||||
* @param NumOfChannels The number of channels (1 for mono, 2 for stereo, etc)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Import Audio From RAW Buffer", Keywords = "PCM, RAW"), Category = "Runtime Audio Importer|Import")
|
||||
void ImportAudioFromRAWBuffer(UPARAM(DisplayName = "RAW Buffer") TArray<uint8> RAWBuffer, UPARAM(DisplayName = "RAW Format") ERuntimeRAWAudioFormat RAWFormat, int32 SampleRate = 44100, int32 NumOfChannels = 1);
|
||||
|
||||
/**
|
||||
* Import audio from a RAW buffer. The audio data must not have headers and must be uncompressed. Suitable for use with 64-bit data size
|
||||
*
|
||||
* @param RAWBuffer The RAW audio buffer
|
||||
* @param RAWFormat RAW audio format
|
||||
* @param SampleRate The number of samples per second
|
||||
* @param NumOfChannels The number of channels (1 for mono, 2 for stereo, etc)
|
||||
*/
|
||||
void ImportAudioFromRAWBuffer(TArray64<uint8> RAWBuffer, ERuntimeRAWAudioFormat RAWFormat, int32 SampleRate = 44100, int32 NumOfChannels = 1);
|
||||
|
||||
/**
|
||||
* Converts a regular SoundWave to an inherited sound wave of type ImportedSoundWave used in RuntimeAudioImporter
|
||||
*
|
||||
* @param SoundWave The regular USoundWave to convert
|
||||
* @param ImportedSoundWaveClass The subclass of UImportedSoundWave to create and convert to
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Runtime Audio Importer|Convert")
|
||||
static void ConvertRegularToImportedSoundWave(USoundWave* SoundWave, TSubclassOf<UImportedSoundWave> ImportedSoundWaveClass, const FOnRegularToAudioImporterSoundWaveConvertResult& Result);
|
||||
|
||||
/**
|
||||
* Converts a regular SoundWave to an inherited sound wave of type ImportedSoundWave used in RuntimeAudioImporter. Suitable for use in C++
|
||||
*
|
||||
* @param SoundWave The regular USoundWave to convert
|
||||
* @param ImportedSoundWaveClass The subclass of UImportedSoundWave to create and convert to
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void ConvertRegularToImportedSoundWave(USoundWave* SoundWave, TSubclassOf<UImportedSoundWave> ImportedSoundWaveClass, const FOnRegularToAudioImporterSoundWaveConvertResultNative& Result);
|
||||
|
||||
/**
|
||||
* Transcode one RAW Data format into another from buffer
|
||||
*
|
||||
* @param RAWDataFrom The RAW audio data to transcode
|
||||
* @param RAWFormatFrom The original format of the RAW audio data
|
||||
* @param RAWFormatTo The desired format of the transcoded RAW audio data
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Transcode RAW Data From Buffer"), Category = "Runtime Audio Importer|Transcode")
|
||||
static void TranscodeRAWDataFromBuffer(UPARAM(DisplayName = "RAW Data From") TArray<uint8> RAWDataFrom, UPARAM(DisplayName = "RAW Format From") ERuntimeRAWAudioFormat RAWFormatFrom, UPARAM(DisplayName = "RAW Format To") ERuntimeRAWAudioFormat RAWFormatTo, const FOnRAWDataTranscodeFromBufferResult& Result);
|
||||
|
||||
/**
|
||||
* Transcode one RAW data format into another from buffer. Suitable for use with 64-bit data size
|
||||
*
|
||||
* @param RAWDataFrom The RAW audio data to transcode
|
||||
* @param RAWFormatFrom The original format of the RAW audio data
|
||||
* @param RAWFormatTo The desired format of the transcoded RAW audio data
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void TranscodeRAWDataFromBuffer(TArray64<uint8> RAWDataFrom, ERuntimeRAWAudioFormat RAWFormatFrom, ERuntimeRAWAudioFormat RAWFormatTo, const FOnRAWDataTranscodeFromBufferResultNative& Result);
|
||||
|
||||
/**
|
||||
* Transcode one RAW data format into another from file
|
||||
*
|
||||
* @param FilePathFrom Path to file with the RAW audio data to transcode
|
||||
* @param RAWFormatFrom The original format of the RAW audio data
|
||||
* @param FilePathTo File path for saving RAW data
|
||||
* @param RAWFormatTo The desired format of the transcoded RAW audio data
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Transcode RAW Data From File"), Category = "Runtime Audio Importer|Transcode")
|
||||
static void TranscodeRAWDataFromFile(const FString& FilePathFrom, UPARAM(DisplayName = "RAW Format From") ERuntimeRAWAudioFormat RAWFormatFrom, const FString& FilePathTo, UPARAM(DisplayName = "RAW Format To") ERuntimeRAWAudioFormat RAWFormatTo, const FOnRAWDataTranscodeFromFileResult& Result);
|
||||
|
||||
/**
|
||||
* Transcode one RAW data format into another from file. Suitable for use in C++
|
||||
*
|
||||
* @param FilePathFrom Path to file with the RAW audio data to transcode
|
||||
* @param RAWFormatFrom The original format of the RAW audio data
|
||||
* @param FilePathTo File path for saving RAW data
|
||||
* @param RAWFormatTo The desired format of the transcoded RAW audio data
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void TranscodeRAWDataFromFile(const FString& FilePathFrom, UPARAM(DisplayName = "RAW Format From") ERuntimeRAWAudioFormat RAWFormatFrom, const FString& FilePathTo, UPARAM(DisplayName = "RAW Format To") ERuntimeRAWAudioFormat RAWFormatTo, const FOnRAWDataTranscodeFromFileResultNative& Result);
|
||||
|
||||
/**
|
||||
* Export the imported sound wave to a file
|
||||
*
|
||||
* @param ImportedSoundWave Imported sound wave to be exported
|
||||
* @param AudioFormat The desired audio format for the exported file. Note that some formats may not be supported
|
||||
* @param SavePath The path where the exported file will be saved
|
||||
* @param Quality The quality of the encoded audio data, from 0 to 100
|
||||
* @param OverrideOptions Override options for the export
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Runtime Audio Importer|Export")
|
||||
static void ExportSoundWaveToFile(UImportedSoundWave* ImportedSoundWave, const FString& SavePath, ERuntimeAudioFormat AudioFormat, uint8 Quality, const FRuntimeAudioExportOverrideOptions& OverrideOptions, const FOnAudioExportToFileResult& Result);
|
||||
|
||||
/**
|
||||
* Export the imported sound wave into file. Suitable for use in C++
|
||||
*
|
||||
* @param ImportedSoundWavePtr Imported sound wave to be exported
|
||||
* @param AudioFormat The desired audio format for the exported file. Note that some formats may not be supported
|
||||
* @param SavePath The path where the exported file will be saved
|
||||
* @param Quality The quality of the encoded audio data, from 0 to 100
|
||||
* @param OverrideOptions Override options for the export
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void ExportSoundWaveToFile(TWeakObjectPtr<UImportedSoundWave> ImportedSoundWavePtr, const FString& SavePath, ERuntimeAudioFormat AudioFormat, uint8 Quality, const FRuntimeAudioExportOverrideOptions& OverrideOptions, const FOnAudioExportToFileResultNative& Result);
|
||||
|
||||
/**
|
||||
* Export the imported sound wave into a buffer
|
||||
*
|
||||
* @param ImportedSoundWave Imported sound wave to be exported
|
||||
* @param AudioFormat The desired audio format for the exported file. Note that some formats may not be supported
|
||||
* @param Quality The quality of the encoded audio data, from 0 to 100
|
||||
* @param OverrideOptions Override options for the export
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Runtime Audio Importer|Export")
|
||||
static void ExportSoundWaveToBuffer(UImportedSoundWave* ImportedSoundWave, ERuntimeAudioFormat AudioFormat, uint8 Quality, const FRuntimeAudioExportOverrideOptions& OverrideOptions, const FOnAudioExportToBufferResult& Result);
|
||||
|
||||
/**
|
||||
* Export the imported sound wave into a buffer. Suitable for use in C++
|
||||
*
|
||||
* @param ImportedSoundWavePtr Imported sound wave to be exported
|
||||
* @param AudioFormat The desired audio format for the exported file. Note that some formats may not be supported
|
||||
* @param Quality The quality of the encoded audio data, from 0 to 100
|
||||
* @param OverrideOptions Override options for the export
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void ExportSoundWaveToBuffer(TWeakObjectPtr<UImportedSoundWave> ImportedSoundWavePtr, ERuntimeAudioFormat AudioFormat, uint8 Quality, const FRuntimeAudioExportOverrideOptions& OverrideOptions, const FOnAudioExportToBufferResultNative& Result);
|
||||
|
||||
/**
|
||||
* Export the imported sound wave into a RAW file
|
||||
*
|
||||
* @param ImportedSoundWave Imported sound wave to be exported
|
||||
* @param RAWFormat Required RAW format for exporting
|
||||
* @param SavePath Path to save the file
|
||||
* @param OverrideOptions Override options for the export
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Export Sound Wave To RAW File"), Category = "Runtime Audio Importer|Export")
|
||||
static void ExportSoundWaveToRAWFile(UImportedSoundWave* ImportedSoundWave, const FString& SavePath, UPARAM(DisplayName = "RAW Format") ERuntimeRAWAudioFormat RAWFormat, const FRuntimeAudioExportOverrideOptions& OverrideOptions, const FOnAudioExportToFileResult& Result);
|
||||
|
||||
/**
|
||||
* Export the imported sound wave into a RAW file. Suitable for use in C++
|
||||
*
|
||||
* @param ImportedSoundWavePtr Imported sound wave to be exported
|
||||
* @param RAWFormat Required RAW format for exporting
|
||||
* @param SavePath Path to save the file
|
||||
* @param OverrideOptions Override options for the export
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void ExportSoundWaveToRAWFile(TWeakObjectPtr<UImportedSoundWave> ImportedSoundWavePtr, const FString& SavePath, ERuntimeRAWAudioFormat RAWFormat, const FRuntimeAudioExportOverrideOptions& OverrideOptions, const FOnAudioExportToFileResultNative& Result);
|
||||
|
||||
/**
|
||||
* Export the imported sound wave into a RAW buffer
|
||||
*
|
||||
* @param ImportedSoundWave Imported sound wave to be exported
|
||||
* @param RAWFormat Required RAW format for exporting
|
||||
* @param OverrideOptions Override options for the export
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Export Sound Wave To RAW Buffer"), Category = "Runtime Audio Importer|Export")
|
||||
static void ExportSoundWaveToRAWBuffer(UImportedSoundWave* ImportedSoundWave, UPARAM(DisplayName = "RAW Format") ERuntimeRAWAudioFormat RAWFormat, const FRuntimeAudioExportOverrideOptions& OverrideOptions, const FOnAudioExportToBufferResult& Result);
|
||||
|
||||
/**
|
||||
* Export the imported sound wave into a RAW buffer. Suitable for use with 64-bit data size
|
||||
*
|
||||
* @param ImportedSoundWavePtr Imported sound wave to be exported
|
||||
* @param RAWFormat Required RAW format for exporting
|
||||
* @param OverrideOptions Override options for the export
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void ExportSoundWaveToRAWBuffer(TWeakObjectPtr<UImportedSoundWave> ImportedSoundWavePtr, ERuntimeRAWAudioFormat RAWFormat, const FRuntimeAudioExportOverrideOptions& OverrideOptions, const FOnAudioExportToBufferResultNative& Result);
|
||||
|
||||
/**
|
||||
* Get the audio format based on file extension
|
||||
*
|
||||
* @param FilePath File path where to find the format (by extension)
|
||||
* @return The found audio format (e.g. mp3. flac, etc)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Runtime Audio Importer|Utilities")
|
||||
static ERuntimeAudioFormat GetAudioFormat(const FString& FilePath);
|
||||
|
||||
/**
|
||||
* Determine the audio format based on audio data. A more advanced way to get the format
|
||||
*
|
||||
* @param AudioData Audio data array
|
||||
* @return The found audio format (e.g. mp3. flac, etc)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Runtime Audio Importer|Utilities")
|
||||
static ERuntimeAudioFormat GetAudioFormatAdvanced(const TArray<uint8>& AudioData);
|
||||
|
||||
/**
|
||||
* Retrieve audio header (metadata) information from a file
|
||||
*
|
||||
* @param FilePath The path to the audio file from which header information will be retrieved
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Runtime Audio Importer|Utilities")
|
||||
static void GetAudioHeaderInfoFromFile(const FString& FilePath, const FOnGetAudioHeaderInfoResult& Result);
|
||||
|
||||
/**
|
||||
* Retrieve audio header (metadata) information from a file. Suitable for use in C++
|
||||
*
|
||||
* @param FilePath The path to the audio file from which header information will be retrieved
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void GetAudioHeaderInfoFromFile(const FString& FilePath, const FOnGetAudioHeaderInfoResultNative& Result);
|
||||
|
||||
/**
|
||||
* Retrieve audio header (metadata) information from a buffer
|
||||
*
|
||||
* @param AudioData The audio data from which the header information will be retrieved
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Runtime Audio Importer|Utilities")
|
||||
static void GetAudioHeaderInfoFromBuffer(TArray<uint8> AudioData, const FOnGetAudioHeaderInfoResult& Result);
|
||||
|
||||
/**
|
||||
* Retrieve audio header (metadata) information from a buffer. Suitable for use with 64-bit data size
|
||||
*
|
||||
* @param AudioData The audio data from which the header information will be retrieved
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void GetAudioHeaderInfoFromBuffer(TArray64<uint8> AudioData, const FOnGetAudioHeaderInfoResultNative& Result);
|
||||
|
||||
/**
|
||||
* Determine audio format based on audio data. A more advanced way to get the format. Suitable for use with 64-bit data size
|
||||
*
|
||||
* @param AudioData Audio data array
|
||||
* @return The found audio format (e.g. mp3. flac, etc)
|
||||
*/
|
||||
static ERuntimeAudioFormat GetAudioFormatAdvanced(const TArray64<uint8>& AudioData);
|
||||
|
||||
/**
|
||||
* Determine audio format based on audio data. A more advanced way to get the format. Suitable for use with 64-bit data size
|
||||
*
|
||||
* @param AudioData Audio data array
|
||||
* @return The found audio format (e.g. mp3. flac, etc)
|
||||
*/
|
||||
static ERuntimeAudioFormat GetAudioFormatAdvanced(const FRuntimeBulkDataBuffer<uint8>& AudioData);
|
||||
|
||||
/**
|
||||
* Convert seconds to string (hh:mm:ss or mm:ss depending on the number of seconds)
|
||||
*
|
||||
* @return hh:mm:ss or mm:ss string representation
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Runtime Audio Importer|Utilities")
|
||||
static FString ConvertSecondsToString(int64 Seconds);
|
||||
|
||||
/**
|
||||
* Scan the specified directory for audio files
|
||||
*
|
||||
* @param Directory The directory path to scan for audio files
|
||||
* @param bRecursive Whether to search for files recursively in subdirectories
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (Keywords = "Folder"), Category = "Runtime Audio Importer|Utilities")
|
||||
static void ScanDirectoryForAudioFiles(const FString& Directory, bool bRecursive, const FOnScanDirectoryForAudioFilesResult& Result);
|
||||
|
||||
/**
|
||||
* Scan the specified directory for audio files. Suitable for use in C++
|
||||
*
|
||||
* @param Directory The directory path to scan for audio files
|
||||
* @param bRecursive Whether to search for files recursively in subdirectories
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void ScanDirectoryForAudioFiles(const FString& Directory, bool bRecursive, const FOnScanDirectoryForAudioFilesResultNative& Result);
|
||||
|
||||
/**
|
||||
* Decode compressed audio data to uncompressed.
|
||||
*
|
||||
* @param EncodedAudioInfo The encoded audio data
|
||||
* @param DecodedAudioInfo The decoded audio data
|
||||
* @return Whether the decoding was successful or not
|
||||
*/
|
||||
static bool DecodeAudioData(FEncodedAudioStruct&& EncodedAudioInfo, FDecodedAudioStruct& DecodedAudioInfo);
|
||||
|
||||
/**
|
||||
* Encode uncompressed audio data to compressed.
|
||||
*
|
||||
* @param DecodedAudioInfo The decoded audio data
|
||||
* @param EncodedAudioInfo The encoded audio data
|
||||
* @param Quality The quality of the encoded audio data, from 0 to 100
|
||||
* @return Whether the encoding was successful or not
|
||||
*/
|
||||
static bool EncodeAudioData(FDecodedAudioStruct&& DecodedAudioInfo, FEncodedAudioStruct& EncodedAudioInfo, uint8 Quality);
|
||||
|
||||
/**
|
||||
* Import audio from 32-bit float PCM data
|
||||
*
|
||||
* @param PCMData PCM data
|
||||
* @param SampleRate The number of samples per second
|
||||
* @param NumOfChannels The number of channels (1 for mono, 2 for stereo, etc)
|
||||
*/
|
||||
void ImportAudioFromFloat32Buffer(FRuntimeBulkDataBuffer<float>&& PCMData, int32 SampleRate = 44100, int32 NumOfChannels = 1);
|
||||
|
||||
/**
|
||||
* Create Imported Sound Wave and finish importing.
|
||||
*
|
||||
* @param DecodedAudioInfo Decoded audio data
|
||||
*/
|
||||
void ImportAudioFromDecodedInfo(FDecodedAudioStruct&& DecodedAudioInfo);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Audio transcoding progress callback
|
||||
*
|
||||
* @param Percentage Percentage of importing completion (0-100%)
|
||||
*/
|
||||
void OnProgress_Internal(int32 Percentage);
|
||||
|
||||
/**
|
||||
* Audio importing finished callback
|
||||
*
|
||||
* @param ImportedSoundWave Reference to the imported sound wave
|
||||
* @param Status Importing status
|
||||
*/
|
||||
void OnResult_Internal(UImportedSoundWave* ImportedSoundWave, ERuntimeImportStatus Status);
|
||||
};
|
@ -0,0 +1,514 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
#include "AudioCaptureDeviceInterface.h"
|
||||
#endif
|
||||
#include "Engine/EngineBaseTypes.h"
|
||||
#include "Sound/SoundGroups.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(4, 26, 0)
|
||||
#include "DSP/BufferVectorOperations.h"
|
||||
#endif
|
||||
|
||||
#include "RuntimeAudioImporterTypes.generated.h"
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(4, 26, 0)
|
||||
namespace Audio
|
||||
{
|
||||
using FAlignedFloatBuffer = Audio::AlignedFloatBuffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Possible audio importing results */
|
||||
UENUM(BlueprintType, Category = "Runtime Audio Importer")
|
||||
enum class ERuntimeImportStatus : uint8
|
||||
{
|
||||
/** Successful import */
|
||||
SuccessfulImport UMETA(DisplayName = "Success"),
|
||||
|
||||
/** 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")
|
||||
};
|
||||
|
||||
/** Possible audio formats (extensions) */
|
||||
UENUM(BlueprintType, Category = "Runtime Audio Importer")
|
||||
enum class ERuntimeAudioFormat : uint8
|
||||
{
|
||||
Auto UMETA(DisplayName = "Determine format automatically"),
|
||||
Mp3 UMETA(DisplayName = "mp3"),
|
||||
Wav UMETA(DisplayName = "wav"),
|
||||
Flac UMETA(DisplayName = "flac"),
|
||||
OggVorbis UMETA(DisplayName = "ogg vorbis"),
|
||||
Bink UMETA(DisplayName = "bink"),
|
||||
Invalid UMETA(DisplayName = "invalid (not defined format, internal use only)", Hidden)
|
||||
};
|
||||
|
||||
/** Possible RAW (uncompressed, PCM) audio formats */
|
||||
UENUM(BlueprintType, Category = "Runtime Audio Importer")
|
||||
enum class ERuntimeRAWAudioFormat : uint8
|
||||
{
|
||||
Int8 UMETA(DisplayName = "Signed 8-bit integer"),
|
||||
UInt8 UMETA(DisplayName = "Unsigned 8-bit integer"),
|
||||
Int16 UMETA(DisplayName = "Signed 16-bit integer"),
|
||||
UInt16 UMETA(DisplayName = "Unsigned 16-bit integer"),
|
||||
Int32 UMETA(DisplayName = "Signed 32-bit integer"),
|
||||
UInt32 UMETA(DisplayName = "Unsigned 32-bit integer"),
|
||||
Float32 UMETA(DisplayName = "Floating point 32-bit")
|
||||
};
|
||||
|
||||
/**
|
||||
* An alternative to FBulkDataBuffer with consistent data types
|
||||
*/
|
||||
template <typename DataType>
|
||||
class FRuntimeBulkDataBuffer
|
||||
{
|
||||
public:
|
||||
#if UE_VERSION_OLDER_THAN(4, 27, 0)
|
||||
using ViewType = TArrayView<DataType>;
|
||||
#else
|
||||
using ViewType = TArrayView64<DataType>;
|
||||
#endif
|
||||
|
||||
FRuntimeBulkDataBuffer() = default;
|
||||
|
||||
FRuntimeBulkDataBuffer(const FRuntimeBulkDataBuffer& Other)
|
||||
{
|
||||
*this = Other;
|
||||
}
|
||||
|
||||
FRuntimeBulkDataBuffer(FRuntimeBulkDataBuffer&& Other) noexcept
|
||||
{
|
||||
View = MoveTemp(Other.View);
|
||||
Other.View = ViewType();
|
||||
}
|
||||
|
||||
FRuntimeBulkDataBuffer(DataType* InBuffer, int64 InNumberOfElements)
|
||||
: View(InBuffer, InNumberOfElements)
|
||||
{
|
||||
#if UE_VERSION_OLDER_THAN(4, 27, 0)
|
||||
check(InNumberOfElements <= TNumericLimits<int32>::Max())
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename Allocator>
|
||||
explicit FRuntimeBulkDataBuffer(const TArray<DataType, Allocator>& Other)
|
||||
{
|
||||
const int64 BulkDataSize = Other.Num();
|
||||
|
||||
DataType* BulkData = static_cast<DataType*>(FMemory::Malloc(BulkDataSize * sizeof(DataType)));
|
||||
if (!BulkData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FMemory::Memcpy(BulkData, Other.GetData(), BulkDataSize * sizeof(DataType));
|
||||
View = ViewType(BulkData, BulkDataSize);
|
||||
}
|
||||
|
||||
~FRuntimeBulkDataBuffer()
|
||||
{
|
||||
FreeBuffer();
|
||||
}
|
||||
|
||||
FRuntimeBulkDataBuffer& operator=(const FRuntimeBulkDataBuffer& Other)
|
||||
{
|
||||
FreeBuffer();
|
||||
|
||||
if (this != &Other)
|
||||
{
|
||||
const int64 BufferSize = Other.View.Num();
|
||||
|
||||
DataType* BufferCopy = static_cast<DataType*>(FMemory::Malloc(BufferSize * sizeof(DataType)));
|
||||
FMemory::Memcpy(BufferCopy, Other.View.GetData(), BufferSize * sizeof(DataType));
|
||||
|
||||
View = ViewType(BufferCopy, BufferSize);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
FRuntimeBulkDataBuffer& operator=(FRuntimeBulkDataBuffer&& Other) noexcept
|
||||
{
|
||||
if (this != &Other)
|
||||
{
|
||||
FreeBuffer();
|
||||
|
||||
View = Other.View;
|
||||
Other.View = ViewType();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Empty()
|
||||
{
|
||||
FreeBuffer();
|
||||
View = ViewType();
|
||||
}
|
||||
|
||||
void Reset(DataType* InBuffer, int64 InNumberOfElements)
|
||||
{
|
||||
FreeBuffer();
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(4, 27, 0)
|
||||
check(InNumberOfElements <= TNumericLimits<int32>::Max())
|
||||
#endif
|
||||
|
||||
View = ViewType(InBuffer, InNumberOfElements);
|
||||
}
|
||||
|
||||
const ViewType& GetView() const
|
||||
{
|
||||
return View;
|
||||
}
|
||||
|
||||
private:
|
||||
void FreeBuffer()
|
||||
{
|
||||
if (View.GetData() != nullptr)
|
||||
{
|
||||
FMemory::Free(View.GetData());
|
||||
View = ViewType();
|
||||
}
|
||||
}
|
||||
|
||||
ViewType View;
|
||||
};
|
||||
|
||||
/** Basic sound wave data */
|
||||
struct FSoundWaveBasicStruct
|
||||
{
|
||||
FSoundWaveBasicStruct()
|
||||
: NumOfChannels(0)
|
||||
, SampleRate(0)
|
||||
, Duration(0)
|
||||
{
|
||||
}
|
||||
|
||||
/** Number of channels */
|
||||
uint32 NumOfChannels;
|
||||
|
||||
/** Sample rate (samples per second, sampling frequency) */
|
||||
uint32 SampleRate;
|
||||
|
||||
/** Sound wave duration, sec */
|
||||
float Duration;
|
||||
|
||||
/**
|
||||
* Whether the sound wave data appear to be valid or not
|
||||
*/
|
||||
bool IsValid() const
|
||||
{
|
||||
return NumOfChannels > 0 && Duration > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the basic sound wave struct to a readable format
|
||||
*
|
||||
* @return String representation of the basic sound wave struct
|
||||
*/
|
||||
FString ToString() const
|
||||
{
|
||||
return FString::Printf(TEXT("Number of channels: %d, sample rate: %d, duration: %f"), NumOfChannels, SampleRate, Duration);
|
||||
}
|
||||
};
|
||||
|
||||
/** PCM data buffer structure */
|
||||
struct FPCMStruct
|
||||
{
|
||||
FPCMStruct()
|
||||
: PCMNumOfFrames(0)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the PCM data appear to be valid or not
|
||||
*/
|
||||
bool IsValid() const
|
||||
{
|
||||
return PCMData.GetView().GetData() && PCMNumOfFrames > 0 && PCMData.GetView().Num() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts PCM struct to a readable format
|
||||
*
|
||||
* @return String representation of the PCM Struct
|
||||
*/
|
||||
FString ToString() const
|
||||
{
|
||||
return FString::Printf(TEXT("Validity of PCM data in memory: %s, number of PCM frames: %d, PCM data size: %lld"),
|
||||
PCMData.GetView().IsValidIndex(0) ? TEXT("Valid") : TEXT("Invalid"), PCMNumOfFrames, static_cast<int64>(PCMData.GetView().Num()));
|
||||
}
|
||||
|
||||
/** 32-bit float PCM data */
|
||||
FRuntimeBulkDataBuffer<float> PCMData;
|
||||
|
||||
/** Number of PCM frames */
|
||||
uint32 PCMNumOfFrames;
|
||||
};
|
||||
|
||||
/** Decoded audio information */
|
||||
struct FDecodedAudioStruct
|
||||
{
|
||||
/**
|
||||
* Whether the decoded audio data appear to be valid or not
|
||||
*/
|
||||
bool IsValid() const
|
||||
{
|
||||
return SoundWaveBasicInfo.IsValid() && PCMInfo.IsValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Decoded Audio Struct to a readable format
|
||||
*
|
||||
* @return String representation of the Decoded Audio Struct
|
||||
*/
|
||||
FString ToString() const
|
||||
{
|
||||
return FString::Printf(TEXT("SoundWave Basic Info:\n%s\n\nPCM Info:\n%s"), *SoundWaveBasicInfo.ToString(), *PCMInfo.ToString());
|
||||
}
|
||||
|
||||
/** SoundWave basic info (e.g. duration, number of channels, etc) */
|
||||
FSoundWaveBasicStruct SoundWaveBasicInfo;
|
||||
|
||||
/** PCM data buffer */
|
||||
FPCMStruct PCMInfo;
|
||||
};
|
||||
|
||||
/** Encoded audio information */
|
||||
struct FEncodedAudioStruct
|
||||
{
|
||||
FEncodedAudioStruct()
|
||||
: AudioFormat(ERuntimeAudioFormat::Invalid)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Allocator>
|
||||
FEncodedAudioStruct(const TArray<uint8, Allocator>& AudioDataArray, ERuntimeAudioFormat AudioFormat)
|
||||
: AudioData(AudioDataArray)
|
||||
, AudioFormat(AudioFormat)
|
||||
{
|
||||
}
|
||||
|
||||
FEncodedAudioStruct(FRuntimeBulkDataBuffer<uint8> AudioDataBulk, ERuntimeAudioFormat AudioFormat)
|
||||
: AudioData(MoveTemp(AudioDataBulk))
|
||||
, AudioFormat(AudioFormat)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Encoded Audio Struct to a readable format
|
||||
*
|
||||
* @return String representation of the Encoded Audio Struct
|
||||
*/
|
||||
FString ToString() const
|
||||
{
|
||||
return FString::Printf(TEXT("Validity of audio data in memory: %s, audio data size: %lld, audio format: %s"),
|
||||
AudioData.GetView().IsValidIndex(0) ? TEXT("Valid") : TEXT("Invalid"), static_cast<int64>(AudioData.GetView().Num()),
|
||||
*UEnum::GetValueAsName(AudioFormat).ToString());
|
||||
}
|
||||
|
||||
/** Audio data */
|
||||
FRuntimeBulkDataBuffer<uint8> AudioData;
|
||||
|
||||
/** Format of the audio data (e.g. mp3, flac, etc) */
|
||||
ERuntimeAudioFormat AudioFormat;
|
||||
};
|
||||
|
||||
/** Compressed sound wave information */
|
||||
USTRUCT(BlueprintType, Category = "Runtime Audio Importer")
|
||||
struct FCompressedSoundWaveInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FCompressedSoundWaveInfo()
|
||||
: SoundGroup(ESoundGroup::SOUNDGROUP_Default)
|
||||
, bLooping(false)
|
||||
, Volume(1.f)
|
||||
, Pitch(1.f)
|
||||
{
|
||||
}
|
||||
|
||||
/** Sound group */
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
TEnumAsByte<ESoundGroup> SoundGroup;
|
||||
|
||||
/** If set, when played directly (not through a sound cue) the wave will be played looping */
|
||||
UPROPERTY(BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
bool bLooping;
|
||||
|
||||
/** Playback volume of sound 0 to 1 - Default is 1.0 */
|
||||
UPROPERTY(BlueprintReadWrite, meta = (ClampMin = "0.0"), Category = "Runtime Audio Importer")
|
||||
float Volume;
|
||||
|
||||
/** Playback pitch for sound. */
|
||||
UPROPERTY(BlueprintReadWrite, meta = (ClampMin = "0.125", ClampMax = "4.0"), Category = "Runtime Audio Importer")
|
||||
float Pitch;
|
||||
};
|
||||
|
||||
/** A line of subtitle text and the time at which it should be displayed. This is the same as FSubtitleCue but editable in Blueprints */
|
||||
USTRUCT(BlueprintType, Category = "Runtime Audio Importer")
|
||||
struct FEditableSubtitleCue
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FEditableSubtitleCue()
|
||||
: Time(0)
|
||||
{
|
||||
}
|
||||
|
||||
/** The text to appear in the subtitle */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
FText Text;
|
||||
|
||||
/** The time at which the subtitle is to be displayed, in seconds relative to the beginning of the line */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
float Time;
|
||||
};
|
||||
|
||||
/** Platform audio input device info */
|
||||
USTRUCT(BlueprintType, Category = "Runtime Audio Importer")
|
||||
struct FRuntimeAudioInputDeviceInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FRuntimeAudioInputDeviceInfo()
|
||||
: DeviceName("")
|
||||
, DeviceId("")
|
||||
, InputChannels(0)
|
||||
, PreferredSampleRate(0)
|
||||
, bSupportsHardwareAEC(true)
|
||||
{
|
||||
}
|
||||
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
FRuntimeAudioInputDeviceInfo(const Audio::FCaptureDeviceInfo& DeviceInfo)
|
||||
: DeviceName(DeviceInfo.DeviceName)
|
||||
#if UE_VERSION_NEWER_THAN(4, 25, 0)
|
||||
, DeviceId(DeviceInfo.DeviceId)
|
||||
#endif
|
||||
, InputChannels(DeviceInfo.InputChannels)
|
||||
, PreferredSampleRate(DeviceInfo.PreferredSampleRate)
|
||||
, bSupportsHardwareAEC(DeviceInfo.bSupportsHardwareAEC)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/** The name of the audio device */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
FString DeviceName;
|
||||
|
||||
/** ID of the device */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
FString DeviceId;
|
||||
|
||||
/** The number of channels supported by the audio device */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
int32 InputChannels;
|
||||
|
||||
/** The preferred sample rate of the audio device */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
int32 PreferredSampleRate;
|
||||
|
||||
/** Whether or not the device supports Acoustic Echo Canceling */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
bool bSupportsHardwareAEC;
|
||||
};
|
||||
|
||||
/** Audio header information */
|
||||
USTRUCT(BlueprintType, Category = "Runtime Audio Importer")
|
||||
struct FRuntimeAudioHeaderInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FRuntimeAudioHeaderInfo()
|
||||
: Duration(0.f)
|
||||
, NumOfChannels(0)
|
||||
, SampleRate(0)
|
||||
, PCMDataSize(0)
|
||||
, AudioFormat(ERuntimeAudioFormat::Invalid)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Audio Header Info to a readable format
|
||||
*
|
||||
* @return String representation of the Encoded Audio Struct
|
||||
*/
|
||||
FString ToString() const
|
||||
{
|
||||
return FString::Printf(TEXT("Duration: %f, number of channels: %d, sample rate: %d, PCM data size: %lld, audio format: %s"),
|
||||
Duration, NumOfChannels, SampleRate, PCMDataSize, *UEnum::GetValueAsName(AudioFormat).ToString());
|
||||
}
|
||||
|
||||
/** Audio duration, sec */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Runtime Audio Importer")
|
||||
float Duration;
|
||||
|
||||
/** Number of channels */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Runtime Audio Importer")
|
||||
int32 NumOfChannels;
|
||||
|
||||
/** Sample rate (samples per second, sampling frequency) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Runtime Audio Importer")
|
||||
int32 SampleRate;
|
||||
|
||||
/** PCM data size in 32-bit float format */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, DisplayName = "PCM Data Size", Category = "Runtime Audio Importer")
|
||||
int64 PCMDataSize;
|
||||
|
||||
/** Format of the source audio data (e.g. mp3, flac, etc) */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Runtime Audio Importer")
|
||||
ERuntimeAudioFormat AudioFormat;
|
||||
};
|
||||
|
||||
/** Audio export override options */
|
||||
USTRUCT(BlueprintType, Category = "Runtime Audio Importer")
|
||||
struct FRuntimeAudioExportOverrideOptions
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FRuntimeAudioExportOverrideOptions()
|
||||
: NumOfChannels(-1)
|
||||
, SampleRate(-1)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsOverriden() const
|
||||
{
|
||||
return IsNumOfChannelsOverriden() || IsSampleRateOverriden();
|
||||
}
|
||||
|
||||
bool IsSampleRateOverriden() const
|
||||
{
|
||||
return SampleRate != -1;
|
||||
}
|
||||
|
||||
bool IsNumOfChannelsOverriden() const
|
||||
{
|
||||
return NumOfChannels != -1;
|
||||
}
|
||||
|
||||
/** Number of channels. Set to -1 to retrieve from source. Mixing if count differs from source */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
int32 NumOfChannels;
|
||||
|
||||
/** Audio sampling rate (samples per second, sampling frequency). Set to -1 to retrieve from source. Resampling if count differs from source */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Runtime Audio Importer")
|
||||
int32 SampleRate;
|
||||
};
|
@ -0,0 +1,90 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
#include "AudioCaptureCore.h"
|
||||
#endif
|
||||
#include "StreamingSoundWave.h"
|
||||
#include "CapturableSoundWave.generated.h"
|
||||
|
||||
/** Static delegate broadcasting available audio input devices */
|
||||
DECLARE_DELEGATE_OneParam(FOnGetAvailableAudioInputDevicesResultNative, const TArray<FRuntimeAudioInputDeviceInfo>&);
|
||||
|
||||
/** Dynamic delegate broadcasting available audio input devices */
|
||||
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnGetAvailableAudioInputDevicesResult, const TArray<FRuntimeAudioInputDeviceInfo>&, AvailableDevices);
|
||||
|
||||
/**
|
||||
* Sound wave that can capture audio data from input devices (eg. microphone)
|
||||
*/
|
||||
UCLASS(BlueprintType, Category = "Capturable Sound Wave")
|
||||
class RUNTIMEAUDIOIMPORTER_API UCapturableSoundWave : public UStreamingSoundWave
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UCapturableSoundWave(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
//~ Begin UImportedSoundWave Interface
|
||||
virtual void BeginDestroy() override;
|
||||
//~ End UImportedSoundWave Interface
|
||||
|
||||
/**
|
||||
* Create a new instance of the capturable sound wave
|
||||
*
|
||||
* @return Created capturable sound wave
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Capturable Sound Wave|Main")
|
||||
static UCapturableSoundWave* CreateCapturableSoundWave();
|
||||
|
||||
/**
|
||||
* Get information about all available audio input devices
|
||||
*
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Capturable Sound Wave|Info")
|
||||
static void GetAvailableAudioInputDevices(const FOnGetAvailableAudioInputDevicesResult& Result);
|
||||
|
||||
/**
|
||||
* Gets information about all audio output devices available in the system. Suitable for use in C++
|
||||
*
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
static void GetAvailableAudioInputDevices(const FOnGetAvailableAudioInputDevicesResultNative& Result);
|
||||
|
||||
/**
|
||||
* Start the capture process
|
||||
*
|
||||
* @param DeviceId Required device index (order as from GetAvailableAudioInputDevices)
|
||||
* @return Whether the capture was started or not
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Capturable Sound Wave|Capture")
|
||||
bool StartCapture(int32 DeviceId);
|
||||
|
||||
/**
|
||||
* Stop the capture process
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Capturable Sound Wave|Capture")
|
||||
void StopCapture();
|
||||
|
||||
/**
|
||||
* Toggles the mute state of audio capture, pausing the accumulation of audio data without closing the stream
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Capturable Sound Wave|Capture")
|
||||
bool ToggleMute(bool bMute);
|
||||
|
||||
/**
|
||||
* Get whether the capture is processing or not
|
||||
*
|
||||
* @return Whether the capture is processing or not
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Capturable Sound Wave|Info")
|
||||
bool IsCapturing() const;
|
||||
|
||||
private:
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT
|
||||
/** Audio capture instance */
|
||||
Audio::FAudioCapture AudioCapture;
|
||||
#endif
|
||||
};
|
@ -0,0 +1,378 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeAudioImporterTypes.h"
|
||||
#include "Sound/SoundWaveProcedural.h"
|
||||
#include "ImportedSoundWave.generated.h"
|
||||
|
||||
/** Static delegate broadcast to track the end of audio playback */
|
||||
DECLARE_MULTICAST_DELEGATE(FOnAudioPlaybackFinishedNative);
|
||||
|
||||
/** Dynamic delegate broadcast to track the end of audio playback */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAudioPlaybackFinished);
|
||||
|
||||
|
||||
/** Static delegate broadcast PCM data during a generation request */
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnGeneratePCMDataNative, const TArray<float>&);
|
||||
|
||||
/** Dynamic delegate broadcast PCM data during a generation request */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGeneratePCMData, const TArray<float>&, PCMData);
|
||||
|
||||
|
||||
/** Static delegate broadcasting the result of preparing a sound wave for MetaSounds */
|
||||
DECLARE_DELEGATE_OneParam(FOnPrepareSoundWaveForMetaSoundsResultNative, bool);
|
||||
|
||||
/** Dynamic delegate broadcasting the result of preparing a sound wave for MetaSounds */
|
||||
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnPrepareSoundWaveForMetaSoundsResult, bool, bSucceeded);
|
||||
|
||||
|
||||
/** Static delegate broadcasting the result of releasing the played audio data */
|
||||
DECLARE_DELEGATE_OneParam(FOnPlayedAudioDataReleaseResultNative, bool);
|
||||
|
||||
/** Dynamic delegate broadcasting the result of releasing the played audio data */
|
||||
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnPlayedAudioDataReleaseResult, bool, bSucceeded);
|
||||
|
||||
|
||||
/** Static delegate broadcast newly populated PCM data */
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnPopulateAudioDataNative, const TArray<float>&);
|
||||
|
||||
/** Dynamic delegate broadcast newly populated PCM data */
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPopulateAudioData, const TArray<float>&, PopulatedAudioData);
|
||||
|
||||
|
||||
/**
|
||||
* Imported sound wave. Assumed to be dynamically populated once from the decoded audio data.
|
||||
* Audio data preparation takes place in the Runtime Audio Importer library
|
||||
*/
|
||||
UCLASS(BlueprintType, Category = "Imported Sound Wave")
|
||||
class RUNTIMEAUDIOIMPORTER_API UImportedSoundWave : public USoundWaveProcedural
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UImportedSoundWave(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Create a new instance of the imported sound wave
|
||||
*
|
||||
* @return Created imported sound wave
|
||||
*/
|
||||
static UImportedSoundWave* CreateImportedSoundWave();
|
||||
|
||||
//~ Begin USoundWave Interface
|
||||
virtual void BeginDestroy() override;
|
||||
virtual void Parse(class FAudioDevice* AudioDevice, const UPTRINT NodeWaveInstanceHash, FActiveSound& ActiveSound, const FSoundParseParameters& ParseParams, TArray<FWaveInstance*>& WaveInstances) override;
|
||||
virtual Audio::EAudioMixerStreamDataFormat::Type GetGeneratedPCMDataFormat() const override;
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
virtual TSharedPtr<Audio::IProxyData> CreateProxyData(const Audio::FProxyDataInitParams& InitParams) override;
|
||||
virtual bool InitAudioResource(FName Format) override;
|
||||
virtual bool IsSeekable() const override;
|
||||
#endif
|
||||
//~ End USoundWave Interface
|
||||
|
||||
//~ Begin USoundWaveProcedural Interface
|
||||
virtual int32 OnGeneratePCMAudio(TArray<uint8>& OutAudio, int32 NumSamples) override;
|
||||
//~ End USoundWaveProcedural Interface
|
||||
|
||||
/**
|
||||
* Populate audio data from decoded info
|
||||
*
|
||||
* @param DecodedAudioInfo Decoded audio data
|
||||
*/
|
||||
virtual void PopulateAudioDataFromDecodedInfo(FDecodedAudioStruct&& DecodedAudioInfo);
|
||||
|
||||
/**
|
||||
* Prepare this sound wave to be able to set wave parameter for MetaSounds
|
||||
*
|
||||
* @param Result Delegate broadcasting the result. Set the wave parameter only after it has been broadcast
|
||||
* @warning This works if bEnableMetaSoundSupport is enabled in RuntimeAudioImporter.Build.cs/RuntimeAudioImporterEditor.Build.cs and only on Unreal Engine version >= 5.2
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|MetaSounds")
|
||||
void PrepareSoundWaveForMetaSounds(const FOnPrepareSoundWaveForMetaSoundsResult& Result);
|
||||
|
||||
/**
|
||||
* Prepare this sound wave to be able to set wave parameter for MetaSounds. Suitable for use in C++
|
||||
*
|
||||
* @param Result Delegate broadcasting the result. Set the wave parameter only after it has been broadcast
|
||||
* @warning This works if bEnableMetaSoundSupport is enabled in RuntimeAudioImporter.Build.cs/RuntimeAudioImporterEditor.Build.cs and only on Unreal Engine version >= 5.2
|
||||
*/
|
||||
void PrepareSoundWaveForMetaSounds(const FOnPrepareSoundWaveForMetaSoundsResultNative& Result);
|
||||
|
||||
/**
|
||||
* Release sound wave data. Call it manually only if you are sure of it
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Miscellaneous")
|
||||
virtual void ReleaseMemory();
|
||||
|
||||
/**
|
||||
* Remove previously played audio data. Adds a duration offset from the removed audio data
|
||||
* This re-allocates all audio data memory, so should not be called too frequently
|
||||
*
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Miscellaneous")
|
||||
void ReleasePlayedAudioData(const FOnPlayedAudioDataReleaseResult& Result);
|
||||
|
||||
/**
|
||||
* Remove previously played audio data. Adds a duration offset from the removed audio data
|
||||
* This re-allocates all audio data memory, so should not be called too frequently
|
||||
* Suitable for use in C++
|
||||
*
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
virtual void ReleasePlayedAudioData(const FOnPlayedAudioDataReleaseResultNative& Result);
|
||||
|
||||
/**
|
||||
* Set whether the sound should loop or not
|
||||
*
|
||||
* @param bLoop Whether the sound should loop or not
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Properties")
|
||||
void SetLooping(bool bLoop);
|
||||
|
||||
/**
|
||||
* Set subtitle cues
|
||||
*
|
||||
* @param InSubtitles Subtitles cues
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Properties")
|
||||
void SetSubtitles(UPARAM(DisplayName = "Subtitles") const TArray<FEditableSubtitleCue>& InSubtitles);
|
||||
|
||||
/**
|
||||
* Set sound playback volume
|
||||
*
|
||||
* @param InVolume Volume
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Properties")
|
||||
void SetVolume(UPARAM(DisplayName = "Volume") float InVolume = 1);
|
||||
|
||||
/**
|
||||
* Set sound playback pitch
|
||||
*
|
||||
* @param InPitch Pitch
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Properties")
|
||||
void SetPitch(UPARAM(DisplayName = "Pitch") float InPitch = 1);
|
||||
|
||||
/**
|
||||
* Rewind the sound for the specified time
|
||||
*
|
||||
* @note This adds a duration offset (relevant if ReleasePlayedAudioData was used)
|
||||
* @param PlaybackTime How long to rewind the sound
|
||||
* @return Whether the sound was rewound or not
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Main")
|
||||
bool RewindPlaybackTime(float PlaybackTime);
|
||||
|
||||
/**
|
||||
* Thread-unsafe equivalent of RewindPlaybackTime
|
||||
* Should only be used if DataGuard is locked
|
||||
* @note This does not add a duration offset
|
||||
*/
|
||||
bool RewindPlaybackTime_Internal(float PlaybackTime);
|
||||
|
||||
// TODO: Make this async
|
||||
/**
|
||||
* Resample the sound wave to the specified sample rate
|
||||
*
|
||||
* @note This is not thread-safe at the moment
|
||||
* @param NewSampleRate The new sample rate
|
||||
* @return Whether the sound wave was resampled or not
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Main")
|
||||
bool ResampleSoundWave(int32 NewSampleRate);
|
||||
|
||||
// TODO: Make this async
|
||||
/**
|
||||
* Change the number of channels of the sound wave
|
||||
*
|
||||
* @note This is not thread-safe at the moment
|
||||
* @param NewNumOfChannels The new number of channels
|
||||
* @return Whether the sound wave was mixed or not
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Main")
|
||||
bool MixSoundWaveChannels(int32 NewNumOfChannels);
|
||||
|
||||
/**
|
||||
* Change the number of frames played back. Used to rewind the sound
|
||||
*
|
||||
* @param NumOfFrames The new number of frames from which to continue playing sound
|
||||
* @return Whether the frames were changed or not
|
||||
*/
|
||||
bool SetNumOfPlayedFrames(uint32 NumOfFrames);
|
||||
|
||||
/**
|
||||
* Thread-unsafe equivalent of SetNumOfPlayedFrames
|
||||
* Should only be used if DataGuard is locked
|
||||
*/
|
||||
bool SetNumOfPlayedFrames_Internal(uint32 NumOfFrames);
|
||||
|
||||
/**
|
||||
* Get the number of frames played back
|
||||
*
|
||||
* @return The number of frames played back
|
||||
*/
|
||||
uint32 GetNumOfPlayedFrames() const;
|
||||
|
||||
/**
|
||||
* Thread-unsafe equivalent of GetNumOfPlayedFrames
|
||||
* Should only be used if DataGuard is locked
|
||||
*/
|
||||
uint32 GetNumOfPlayedFrames_Internal() const;
|
||||
|
||||
/**
|
||||
* Get the current sound wave playback time, in seconds
|
||||
* @note This adds a duration offset (relevant if ReleasePlayedAudioData was used)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info")
|
||||
float GetPlaybackTime() const;
|
||||
|
||||
/**
|
||||
* Thread-unsafe equivalent of GetPlaybackTime
|
||||
* Should only be used if DataGuard is locked
|
||||
* @note This does not add a duration offset
|
||||
*/
|
||||
float GetPlaybackTime_Internal() const;
|
||||
|
||||
/**
|
||||
* Constant alternative for getting the length of the sound wave, in seconds
|
||||
* @note This adds a duration offset (relevant if ReleasePlayedAudioData was used)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info", meta = (DisplayName = "Get Duration"))
|
||||
float GetDurationConst() const;
|
||||
|
||||
/**
|
||||
* Thread-unsafe equivalent of GetDurationConst
|
||||
* Should only be used if DataGuard is locked
|
||||
* @note This does not add a duration offset
|
||||
*/
|
||||
float GetDurationConst_Internal() const;
|
||||
|
||||
/**
|
||||
* Get the length of the sound wave, in seconds
|
||||
* @note This adds a duration offset (relevant if ReleasePlayedAudioData was used)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info")
|
||||
virtual float GetDuration()
|
||||
#if UE_VERSION_OLDER_THAN(5, 0, 0)
|
||||
override;
|
||||
#else
|
||||
const override;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get sample rate
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info")
|
||||
int32 GetSampleRate() const;
|
||||
|
||||
/**
|
||||
* Get number of channels
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info")
|
||||
int32 GetNumOfChannels() const;
|
||||
|
||||
/**
|
||||
* Get the current sound playback percentage, 0-100%
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info")
|
||||
float GetPlaybackPercentage() const;
|
||||
|
||||
/**
|
||||
* Check if audio playback has finished or not
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info")
|
||||
bool IsPlaybackFinished() const;
|
||||
|
||||
/**
|
||||
* Get the duration offset if some played back audio data was removed during playback (eg in ReleasePlayedAudioData)
|
||||
* The sound wave starts playing from this time as from the very beginning
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info")
|
||||
float GetDurationOffset() const;
|
||||
|
||||
/**
|
||||
* Thread-unsafe equivalent of GetDurationOffset
|
||||
* Should only be used if DataGuard is locked
|
||||
*/
|
||||
float GetDurationOffset_Internal() const;
|
||||
|
||||
/**
|
||||
* Thread-unsafe equivalent of IsPlaybackFinished
|
||||
* Should only be used if DataGuard is locked
|
||||
*/
|
||||
bool IsPlaybackFinished_Internal() const;
|
||||
|
||||
/**
|
||||
* Retrieve audio header (metadata) information. Needed primarily for consistency with the RuntimeAudioImporterLibrary
|
||||
*
|
||||
* @param HeaderInfo Header info, valid only if the return is true
|
||||
* @return Whether the retrieval was successful or not
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info")
|
||||
bool GetAudioHeaderInfo(FRuntimeAudioHeaderInfo& HeaderInfo) const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Makes it possible to broadcast OnAudioPlaybackFinished again
|
||||
*/
|
||||
void ResetPlaybackFinish();
|
||||
|
||||
public:
|
||||
/** Bind to this delegate to know when the audio playback is finished. Suitable for use in C++ */
|
||||
FOnAudioPlaybackFinishedNative OnAudioPlaybackFinishedNative;
|
||||
|
||||
/** Bind to this delegate to know when the audio playback is finished */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Imported Sound Wave|Delegates")
|
||||
FOnAudioPlaybackFinished OnAudioPlaybackFinished;
|
||||
|
||||
/** Bind to this delegate to receive PCM data during playback (may be useful for analyzing audio data). Suitable for use in C++ */
|
||||
FOnGeneratePCMDataNative OnGeneratePCMDataNative;
|
||||
|
||||
/** Bind to this delegate to receive PCM data during playback (may be useful for analyzing audio data) */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Imported Sound Wave|Delegates")
|
||||
FOnGeneratePCMData OnGeneratePCMData;
|
||||
|
||||
/** Bind to this delegate to obtain audio data every time it is populated. Suitable for use in C++ */
|
||||
FOnPopulateAudioDataNative OnPopulateAudioDataNative;
|
||||
|
||||
/** Bind to this delegate to obtain audio data every time it is populated */
|
||||
UPROPERTY(BlueprintAssignable, Category = "Imported Sound Wave|Delegates")
|
||||
FOnPopulateAudioData OnPopulateAudioData;
|
||||
|
||||
/**
|
||||
* Retrieve the PCM buffer, completely thread-safe. Suitable for use in Blueprints
|
||||
*
|
||||
* @return PCM buffer in 32-bit float format
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Sound Wave|Info", meta = (DisplayName = "Get PCM Buffer"))
|
||||
TArray<float> GetPCMBufferCopy();
|
||||
|
||||
/**
|
||||
* Get immutable PCM buffer. Use DataGuard to make it thread safe
|
||||
* Use PopulateAudioDataFromDecodedInfo to populate it
|
||||
*
|
||||
* @return PCM buffer in 32-bit float format
|
||||
*/
|
||||
const FPCMStruct& GetPCMBuffer() const;
|
||||
|
||||
/** Data guard (mutex) for thread safety */
|
||||
mutable FCriticalSection DataGuard;
|
||||
|
||||
protected:
|
||||
/** Duration offset, needed to track the clearing of part of the audio data of the sound wave during playback (see ReleasePlayedAudioData) */
|
||||
float DurationOffset;
|
||||
|
||||
/** Bool to control the behaviour of the OnAudioPlaybackFinished delegate */
|
||||
bool PlaybackFinishedBroadcast;
|
||||
|
||||
/** The number of frames played. Increments during playback, should not be > PCMBufferInfo.PCMNumOfFrames */
|
||||
uint32 PlayedNumOfFrames;
|
||||
|
||||
/** Contains PCM data for sound wave playback */
|
||||
TUniquePtr<FPCMStruct> PCMBufferInfo;
|
||||
|
||||
/** Whether to stop the sound at the end of playback or not. Sound wave will not be garbage collected if playback was completed while this parameter is set to false */
|
||||
bool bStopSoundOnPlaybackFinish;
|
||||
};
|
@ -0,0 +1,92 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "ImportedSoundWave.h"
|
||||
#include "StreamingSoundWave.generated.h"
|
||||
|
||||
/** Static delegate broadcast the result of audio data pre-allocation */
|
||||
DECLARE_DELEGATE_OneParam(FOnPreAllocateAudioDataResultNative, bool);
|
||||
|
||||
/** Dynamic delegate broadcast the result of audio data pre-allocation */
|
||||
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnPreAllocateAudioDataResult, bool, bSucceeded);
|
||||
|
||||
/**
|
||||
* Streaming sound wave. Can append audio data dynamically, including during playback
|
||||
* It will live indefinitely, even if the sound wave has finished playing, until SetStopSoundOnPlaybackFinish is called.
|
||||
* Audio data is always accumulated, clear memory manually via ReleaseMemory or ReleasePlayedAudioData if necessary.
|
||||
*/
|
||||
UCLASS(BlueprintType, Category = "Streaming Sound Wave")
|
||||
class RUNTIMEAUDIOIMPORTER_API UStreamingSoundWave : public UImportedSoundWave
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UStreamingSoundWave(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Create a new instance of the streaming sound wave
|
||||
*
|
||||
* @return Created streaming sound wave
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Streaming Sound Wave|Main")
|
||||
static UStreamingSoundWave* CreateStreamingSoundWave();
|
||||
|
||||
/**
|
||||
* Pre-allocate PCM data, to avoid reallocating memory each time audio data is appended
|
||||
*
|
||||
* @param NumOfBytesToPreAllocate Number of bytes to pre-allocate
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Streaming Sound Wave|Allocation")
|
||||
void PreAllocateAudioData(int64 NumOfBytesToPreAllocate, const FOnPreAllocateAudioDataResult& Result);
|
||||
|
||||
/**
|
||||
* Pre-allocate PCM data, to avoid reallocating memory each time audio data is appended. Suitable for use in C++
|
||||
*
|
||||
* @param NumOfBytesToPreAllocate Number of bytes to pre-allocate
|
||||
* @param Result Delegate broadcasting the result
|
||||
*/
|
||||
void PreAllocateAudioData(int64 NumOfBytesToPreAllocate, const FOnPreAllocateAudioDataResultNative& Result);
|
||||
|
||||
/**
|
||||
* Append audio data to the end of existing data from encoded audio data
|
||||
*
|
||||
* @param AudioData Audio data array
|
||||
* @param AudioFormat Audio format
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Streaming Sound Wave|Append")
|
||||
void AppendAudioDataFromEncoded(TArray<uint8> AudioData, ERuntimeAudioFormat AudioFormat);
|
||||
|
||||
/**
|
||||
* Append audio data to the end of existing data from RAW audio data
|
||||
*
|
||||
* @param RAWData RAW audio buffer
|
||||
* @param RAWFormat RAW audio format
|
||||
* @param InSampleRate The number of samples per second
|
||||
* @param NumOfChannels The number of channels (1 for mono, 2 for stereo, etc)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Audio Data From RAW"), Category = "Streaming Sound Wave|Append")
|
||||
void AppendAudioDataFromRAW(UPARAM(DisplayName = "RAW Data") TArray<uint8> RAWData, UPARAM(DisplayName = "RAW Format") ERuntimeRAWAudioFormat RAWFormat, UPARAM(DisplayName = "Sample Rate") int32 InSampleRate = 44100, int32 NumOfChannels = 1);
|
||||
|
||||
/**
|
||||
* Set whether the sound should stop after playback is complete or not (play "blank sound"). False by default
|
||||
* Setting it to True also makes the sound wave eligible for garbage collection after it has finished playing
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Imported Streaming Sound Wave|Import")
|
||||
void SetStopSoundOnPlaybackFinish(bool bStop);
|
||||
|
||||
//~ Begin UImportedSoundWave Interface
|
||||
virtual void PopulateAudioDataFromDecodedInfo(FDecodedAudioStruct&& DecodedAudioInfo) override;
|
||||
virtual void ReleaseMemory() override;
|
||||
virtual void ReleasePlayedAudioData(const FOnPlayedAudioDataReleaseResultNative& Result) override;
|
||||
//~ End UImportedSoundWave Interface
|
||||
|
||||
private:
|
||||
/** Whether the initial audio data is filled in or not */
|
||||
bool bFilledInitialAudioData;
|
||||
|
||||
/** Number of pre-allocated byte data for PCM */
|
||||
int64 NumOfPreAllocatedByteData;
|
||||
};
|
@ -0,0 +1,144 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
using UnrealBuildTool;
|
||||
using System.IO;
|
||||
|
||||
public class RuntimeAudioImporter : ModuleRules
|
||||
{
|
||||
public RuntimeAudioImporter(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
// Change to toggle MetaSounds support
|
||||
bool bEnableMetaSoundSupport = false;
|
||||
|
||||
// MetaSound is only supported in Unreal Engine version >= 5.3
|
||||
bEnableMetaSoundSupport &= (Target.Version.MajorVersion == 5 && Target.Version.MinorVersion >= 3) || Target.Version.MajorVersion > 5;
|
||||
|
||||
// Disable if you are not using audio input capture
|
||||
bool bEnableCaptureInputSupport = true;
|
||||
|
||||
// Bink format is only supported in Unreal Engine version >= 5
|
||||
bool bEnableBinkSupport = Target.Version.MajorVersion >= 5;
|
||||
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
AddEngineThirdPartyPrivateStaticDependencies(Target,
|
||||
"UEOgg",
|
||||
"Vorbis"
|
||||
);
|
||||
|
||||
// This is necessary because the Vorbis module does not include the Unix-specific libvorbis encoder library
|
||||
if (Target.Platform != UnrealTargetPlatform.IOS && !Target.IsInPlatformGroup(UnrealPlatformGroup.Android) && Target.Platform != UnrealTargetPlatform.Mac && Target.IsInPlatformGroup(UnrealPlatformGroup.Unix))
|
||||
{
|
||||
string VorbisLibPath = Path.Combine(Target.UEThirdPartySourceDirectory, "Vorbis", "libvorbis-1.3.2", "lib");
|
||||
PublicAdditionalLibraries.Add(Path.Combine(VorbisLibPath, "Unix",
|
||||
#if UE_5_2_OR_LATER
|
||||
Target.Architecture.LinuxName,
|
||||
#else
|
||||
Target.Architecture,
|
||||
#endif
|
||||
"libvorbisenc.a"));
|
||||
}
|
||||
|
||||
PublicDefinitions.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"DR_WAV_IMPLEMENTATION=1",
|
||||
"DR_MP3_IMPLEMENTATION=1",
|
||||
"DR_FLAC_IMPLEMENTATION=1"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Core",
|
||||
"AudioPlatformConfiguration"
|
||||
}
|
||||
);
|
||||
|
||||
if (Target.Version.MajorVersion >= 5 && Target.Version.MinorVersion >= 2)
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"AudioExtensions"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (bEnableMetaSoundSupport)
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"MetasoundEngine",
|
||||
"MetasoundFrontend",
|
||||
"MetasoundGraphCore"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
PublicDefinitions.Add(string.Format("WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT={0}", (bEnableMetaSoundSupport ? "1" : "0")));
|
||||
|
||||
if (bEnableCaptureInputSupport)
|
||||
{
|
||||
if (Target.Platform.IsInGroup(UnrealPlatformGroup.Windows) ||
|
||||
Target.Platform == UnrealTargetPlatform.Mac)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("AudioCaptureRtAudio");
|
||||
}
|
||||
else if (Target.Platform == UnrealTargetPlatform.IOS)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("AudioCaptureAudioUnit");
|
||||
}
|
||||
else if (Target.Platform == UnrealTargetPlatform.Android)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("AudioCaptureAndroid");
|
||||
}
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"AudioMixer",
|
||||
"AudioCaptureCore"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
PublicDefinitions.Add(string.Format("WITH_RUNTIMEAUDIOIMPORTER_CAPTURE_SUPPORT={0}", (bEnableCaptureInputSupport ? "1" : "0")));
|
||||
|
||||
if (bEnableBinkSupport)
|
||||
{
|
||||
PrivateDependencyModuleNames.Add("BinkAudioDecoder");
|
||||
|
||||
PublicSystemIncludePaths.Add(Path.Combine(EngineDirectory, "Source", "Runtime", "BinkAudioDecoder", "SDK", "BinkAudio", "Include"));
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.Win64)
|
||||
{
|
||||
PublicAdditionalLibraries.Add(Path.Combine(EngineDirectory, "Source", "Runtime", "BinkAudioDecoder", "SDK", "BinkAudio", "Lib", "binka_ue_encode_win64_static.lib"));
|
||||
}
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.Linux)
|
||||
{
|
||||
PublicAdditionalLibraries.Add(Path.Combine(EngineDirectory, "Source", "Runtime", "BinkAudioDecoder", "SDK", "BinkAudio", "Lib", "libbinka_ue_encode_lnx64_static.a"));
|
||||
}
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.Mac)
|
||||
{
|
||||
if (Target.Version.MajorVersion >= 5 && Target.Version.MinorVersion >= 1)
|
||||
{
|
||||
PublicAdditionalLibraries.Add(Path.Combine(EngineDirectory, "Source", "Runtime", "BinkAudioDecoder", "SDK", "BinkAudio", "Lib", "libbinka_ue_encode_osx_static.a"));
|
||||
}
|
||||
else
|
||||
{
|
||||
PublicAdditionalLibraries.Add(Path.Combine(EngineDirectory, "Source", "Runtime", "BinkAudioDecoder", "SDK", "BinkAudio", "Lib", "libbinka_ue_encode_osx64_static.a"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PublicDefinitions.Add(string.Format("WITH_RUNTIMEAUDIOIMPORTER_BINK_DECODE_SUPPORT={0}", (bEnableBinkSupport ? "1" : "0")));
|
||||
PublicDefinitions.Add(string.Format("WITH_RUNTIMEAUDIOIMPORTER_BINK_ENCODE_SUPPORT={0}", (bEnableBinkSupport && (Target.Platform == UnrealTargetPlatform.Win64 || Target.Platform == UnrealTargetPlatform.Linux || Target.Platform == UnrealTargetPlatform.Mac) ? "1" : "0")));
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "PreImportedSoundFactory.h"
|
||||
#include "PreImportedSoundAsset.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "RuntimeAudioImporterLibrary.h"
|
||||
#include "Logging/MessageLog.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "PreImportedSoundFactory"
|
||||
DEFINE_LOG_CATEGORY(LogPreImportedSoundFactory);
|
||||
|
||||
UPreImportedSoundFactory::UPreImportedSoundFactory()
|
||||
{
|
||||
Formats.Add(TEXT("imp;Runtime Audio Importer any supported format (mp3, wav, flac and ogg)"));
|
||||
|
||||
// Removed for consistency with non-RuntimeAudioImporter modules
|
||||
/*
|
||||
Formats.Add(TEXT("mp3;MPEG-2 Audio"));
|
||||
Formats.Add(TEXT("wav;Wave Audio File"));
|
||||
Formats.Add(TEXT("flac;Free Lossless Audio Codec"));
|
||||
Formats.Add(TEXT("ogg;OGG Vorbis bitstream format"));
|
||||
*/
|
||||
|
||||
SupportedClass = StaticClass();
|
||||
bCreateNew = false; // turned off for import
|
||||
bEditAfterNew = false; // turned off for import
|
||||
bEditorImport = true;
|
||||
bText = false;
|
||||
}
|
||||
|
||||
bool UPreImportedSoundFactory::FactoryCanImport(const FString& Filename)
|
||||
{
|
||||
const FString FileExtension{FPaths::GetExtension(Filename).ToLower()};
|
||||
return FileExtension == TEXT("imp") || URuntimeAudioImporterLibrary::GetAudioFormat(Filename) != ERuntimeAudioFormat::Invalid;
|
||||
}
|
||||
|
||||
UObject* UPreImportedSoundFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Params, FFeedbackContext* Warn, bool& bOutOperationCanceled)
|
||||
{
|
||||
TArray<uint8> AudioData;
|
||||
|
||||
if (!FFileHelper::LoadFileToArray(AudioData, *Filename))
|
||||
{
|
||||
FMessageLog("Import").Error(FText::Format(LOCTEXT("PreImportedSoundFactory_ReadError", "Unable to read the audio file '{0}'. Check file permissions'"), FText::FromString(Filename)));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Removing unused two uninitialized bytes
|
||||
AudioData.RemoveAt(AudioData.Num() - 2, 2);
|
||||
|
||||
FDecodedAudioStruct DecodedAudioInfo;
|
||||
FEncodedAudioStruct EncodedAudioInfo = FEncodedAudioStruct(AudioData, ERuntimeAudioFormat::Auto);
|
||||
|
||||
if (!URuntimeAudioImporterLibrary::DecodeAudioData(MoveTemp(EncodedAudioInfo), DecodedAudioInfo))
|
||||
{
|
||||
FMessageLog("Import").Error(FText::Format(LOCTEXT("PreImportedSoundFactory_DecodeError", "Unable to decode the audio file '{0}'. Make sure the file is not corrupted'"), FText::FromString(Filename)));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UPreImportedSoundAsset* PreImportedSoundAsset = NewObject<UPreImportedSoundAsset>(InParent, UPreImportedSoundAsset::StaticClass(), InName, Flags);
|
||||
PreImportedSoundAsset->AudioDataArray = AudioData;
|
||||
PreImportedSoundAsset->AudioFormat = EncodedAudioInfo.AudioFormat;
|
||||
PreImportedSoundAsset->SourceFilePath = Filename;
|
||||
|
||||
PreImportedSoundAsset->SoundDuration = URuntimeAudioImporterLibrary::ConvertSecondsToString(DecodedAudioInfo.SoundWaveBasicInfo.Duration);
|
||||
PreImportedSoundAsset->NumberOfChannels = DecodedAudioInfo.SoundWaveBasicInfo.NumOfChannels;
|
||||
PreImportedSoundAsset->SampleRate = DecodedAudioInfo.SoundWaveBasicInfo.SampleRate;
|
||||
|
||||
bOutOperationCanceled = false;
|
||||
|
||||
UE_LOG(LogPreImportedSoundFactory, Log, TEXT("Successfully imported sound asset '%s'"), *Filename);
|
||||
|
||||
return PreImportedSoundAsset;
|
||||
}
|
||||
|
||||
bool UPreImportedSoundFactory::CanReimport(UObject* Obj, TArray<FString>& OutFilenames)
|
||||
{
|
||||
if (const UPreImportedSoundAsset* PreImportedSoundAsset = Cast<UPreImportedSoundAsset>(Obj))
|
||||
{
|
||||
OutFilenames.Add(PreImportedSoundAsset->SourceFilePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UPreImportedSoundFactory::SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths)
|
||||
{
|
||||
UPreImportedSoundAsset* PreImportedSoundAsset = Cast<UPreImportedSoundAsset>(Obj);
|
||||
|
||||
if (PreImportedSoundAsset && ensure(NewReimportPaths.Num() == 1))
|
||||
{
|
||||
PreImportedSoundAsset->SourceFilePath = NewReimportPaths[0];
|
||||
}
|
||||
}
|
||||
|
||||
EReimportResult::Type UPreImportedSoundFactory::Reimport(UObject* Obj)
|
||||
{
|
||||
const UPreImportedSoundAsset* PreImportedSoundAsset = Cast<UPreImportedSoundAsset>(Obj);
|
||||
|
||||
if (!PreImportedSoundAsset)
|
||||
{
|
||||
UE_LOG(LogPreImportedSoundFactory, Log, TEXT("The sound asset '%s' cannot be re-imported because the object is corrupted"), *PreImportedSoundAsset->SourceFilePath);
|
||||
return EReimportResult::Failed;
|
||||
}
|
||||
|
||||
if (PreImportedSoundAsset->SourceFilePath.IsEmpty() || !FPaths::FileExists(PreImportedSoundAsset->SourceFilePath))
|
||||
{
|
||||
UE_LOG(LogPreImportedSoundFactory, Log, TEXT("The sound asset '%s' cannot be re-imported because the path to the source file cannot be found"), *PreImportedSoundAsset->SourceFilePath);
|
||||
return EReimportResult::Failed;
|
||||
}
|
||||
|
||||
bool OutCanceled = false;
|
||||
if (ImportObject(Obj->GetClass(), Obj->GetOuter(), *Obj->GetName(), RF_Public | RF_Standalone, PreImportedSoundAsset->SourceFilePath, nullptr, OutCanceled))
|
||||
{
|
||||
UE_LOG(LogPreImportedSoundFactory, Log, TEXT("Successfully re-imported sound asset '%s'"), *PreImportedSoundAsset->SourceFilePath);
|
||||
return EReimportResult::Succeeded;
|
||||
}
|
||||
|
||||
return OutCanceled ? EReimportResult::Cancelled : EReimportResult::Failed;
|
||||
}
|
||||
|
||||
int32 UPreImportedSoundFactory::GetPriority() const
|
||||
{
|
||||
return ImportPriority;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
@ -0,0 +1,28 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#include "RuntimeAudioImporterEditor.h"
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
#include "MetasoundDataReference.h"
|
||||
#include "MetasoundEditorModule.h"
|
||||
#endif
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FRuntimeAudioImporterEditorModule"
|
||||
|
||||
void FRuntimeAudioImporterEditorModule::StartupModule()
|
||||
{
|
||||
#if WITH_RUNTIMEAUDIOIMPORTER_METASOUND_SUPPORT
|
||||
using namespace Metasound;
|
||||
using namespace Metasound::Editor;
|
||||
IMetasoundEditorModule& MetaSoundEditorModule = FModuleManager::GetModuleChecked<IMetasoundEditorModule>("MetaSoundEditor");
|
||||
MetaSoundEditorModule.RegisterPinType("ImportedWave");
|
||||
MetaSoundEditorModule.RegisterPinType(CreateArrayTypeNameFromElementTypeName("ImportedWave"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void FRuntimeAudioImporterEditorModule::ShutdownModule()
|
||||
{
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FRuntimeAudioImporterEditorModule, RuntimeAudioImporterEditor)
|
@ -0,0 +1,41 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "EditorReimportHandler.h"
|
||||
|
||||
#include "Logging/LogCategory.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
#include "Logging/LogVerbosity.h"
|
||||
|
||||
#include "Factories/Factory.h"
|
||||
#include "PreImportedSoundFactory.generated.h"
|
||||
|
||||
/** Declaring custom logging */
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogPreImportedSoundFactory, Log, All);
|
||||
|
||||
/**
|
||||
* Factory for pre-importing audio files. Supports all formats from EAudioFormat, but OGG Vorbis is recommended due to its smaller size and better quality
|
||||
*/
|
||||
UCLASS()
|
||||
class RUNTIMEAUDIOIMPORTEREDITOR_API UPreImportedSoundFactory : public UFactory, public FReimportHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Default constructor */
|
||||
UPreImportedSoundFactory();
|
||||
|
||||
//~ Begin UFactory Interface.
|
||||
virtual bool FactoryCanImport(const FString& Filename) override;
|
||||
virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Params, FFeedbackContext* Warn, bool& bOutOperationCanceled) override;
|
||||
//~ end UFactory Interface.
|
||||
|
||||
//~ Begin FReimportHandler Interface.
|
||||
virtual bool CanReimport(UObject* Obj, TArray<FString>& OutFilenames) override;
|
||||
virtual void SetReimportPaths(UObject* Obj, const TArray<FString>& NewReimportPaths) override;
|
||||
virtual EReimportResult::Type Reimport(UObject* Obj) override;
|
||||
virtual int32 GetPriority() const override;
|
||||
//~ End FReimportHandler Interface.
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FRuntimeAudioImporterEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
@ -0,0 +1,46 @@
|
||||
// Georgy Treshchev 2023.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class RuntimeAudioImporterEditor : ModuleRules
|
||||
{
|
||||
public RuntimeAudioImporterEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
// Change to toggle MetaSounds support
|
||||
bool bEnableMetaSoundSupport = false;
|
||||
|
||||
// MetaSound is only supported in Unreal Engine version >= 5.3
|
||||
bEnableMetaSoundSupport &= (Target.Version.MajorVersion == 5 && Target.Version.MinorVersion >= 3) || Target.Version.MajorVersion > 5;
|
||||
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"RuntimeAudioImporter"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"UnrealEd"
|
||||
}
|
||||
);
|
||||
|
||||
if (bEnableMetaSoundSupport)
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"MetasoundGraphCore",
|
||||
"MetasoundFrontend",
|
||||
"MetasoundEditor"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
12532
Plugins/RuntimeAudioImporter/Source/ThirdParty/dr_flac.h
vendored
Normal file
12532
Plugins/RuntimeAudioImporter/Source/ThirdParty/dr_flac.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4823
Plugins/RuntimeAudioImporter/Source/ThirdParty/dr_mp3.h
vendored
Normal file
4823
Plugins/RuntimeAudioImporter/Source/ThirdParty/dr_mp3.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
8668
Plugins/RuntimeAudioImporter/Source/ThirdParty/dr_wav.h
vendored
Normal file
8668
Plugins/RuntimeAudioImporter/Source/ThirdParty/dr_wav.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ public class Cut5 : ModuleRules
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { });
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore", "UMG", "OpenCV", "DesktopPlatform"});
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore", "UMG", "OpenCV", "DesktopPlatform", "RuntimeAudioImporter"});
|
||||
|
||||
// Uncomment if you are using online features
|
||||
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
|
||||
|
@ -29,7 +29,8 @@ public:
|
||||
virtual void OnUpdateLightArray(const TArray<FColor>& LightArray) {};
|
||||
virtual void OnUpdatePlayers(TSharedPtr<class IWidgetInterface> TrackBody, FColor PlayerColor) {};
|
||||
virtual void OnAddNewTrack(ETrackType Type) {};
|
||||
|
||||
virtual TObjectPtr<UAudioComponent> OnPlaySound(USoundWave* Sound, float StartTime) { return nullptr; };
|
||||
virtual void OnStopSound(TObjectPtr<UAudioComponent> Component) {};
|
||||
|
||||
virtual FString GetGroupName(TSharedPtr<class IWidgetInterface> WidgetInterface) { return FString(); };
|
||||
|
||||
|
@ -3,6 +3,9 @@
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <opencv2/videoio.hpp>
|
||||
|
||||
#include "IImageWrapperModule.h"
|
||||
#include "ImageUtils.h"
|
||||
|
||||
int32 FOpencvUtils::GetVideoFrameCount(FString VideoPath)
|
||||
{
|
||||
cv::VideoCapture VideoCapture;
|
||||
@ -85,3 +88,42 @@ TArray<FColor> FOpencvUtils::GetVideoSingleLightColor(FString VideoPath)
|
||||
VideoCapture.release();
|
||||
return LightArray;
|
||||
}
|
||||
|
||||
FString FOpencvUtils::GetVideoFrameIconName(FString VideoPath)
|
||||
{
|
||||
cv::VideoCapture VideoCapture;
|
||||
VideoCapture.open(TCHAR_TO_UTF8(*VideoPath));
|
||||
TArray<FColor> LightArray;
|
||||
while (VideoCapture.isOpened())
|
||||
{
|
||||
cv::Mat Array;
|
||||
if (VideoCapture.grab())
|
||||
{
|
||||
VideoCapture.retrieve(Array);
|
||||
cv::resize(Array, Array, cv::Size(600, 360));
|
||||
uint8* RGBAData = new uint8[Array.cols * Array.rows * 4];
|
||||
for (int i = 0; i < Array.cols * Array.rows; i++)
|
||||
{
|
||||
RGBAData[i * 4 + 0] = Array.data[i * 3 + 2];
|
||||
RGBAData[i * 4 + 1] = Array.data[i * 3 + 1];
|
||||
RGBAData[i * 4 + 2] = Array.data[i * 3 + 0];
|
||||
RGBAData[i * 4 + 3] = 255;
|
||||
LightArray.Add(FColor(RGBAData[i * 4 + 0], RGBAData[i * 4 + 1], RGBAData[i * 4 + 2], RGBAData[i * 4 + 3]));
|
||||
}
|
||||
delete[] RGBAData;
|
||||
break;
|
||||
}
|
||||
if (Array.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
VideoCapture.release();
|
||||
|
||||
TArray64<uint8> PNGCompress;
|
||||
FString SavePath = FPaths::ProjectDir() + "/Temp/" + FGuid::NewGuid().ToString() + ".png";
|
||||
FImageUtils::PNGCompressImageArray(600, 480, LightArray, PNGCompress);
|
||||
FFileHelper::SaveArrayToFile(PNGCompress, *SavePath);
|
||||
return SavePath;
|
||||
|
||||
}
|
||||
|
@ -8,5 +8,6 @@ public:
|
||||
static int32 GetVideoFrameCount(FString VideoPath);
|
||||
static TArray<TArray<FColor>> GetVideoFrameLightArray(FString VideoPath, int32 X, int32 Y);
|
||||
static TArray<FColor> GetVideoSingleLightColor(FString VideoPath);
|
||||
static FString GetVideoFrameIconName(FString VideoPath);
|
||||
};
|
||||
|
||||
|
@ -12,7 +12,7 @@ public:
|
||||
inline static double DefaultTimeTickSpace = 9.0;
|
||||
inline static int32 LightArrayX = 70;
|
||||
inline static int32 LightArrayY = 42;
|
||||
|
||||
inline static int32 GlobalFPS = 30;
|
||||
static int32 GetAlignOfTickSpace(double Align, bool bCeil = false)
|
||||
{
|
||||
return bCeil ? FMath::CeilToInt(Align / FGlobalData::DefaultTimeTickSpace) * FGlobalData::DefaultTimeTickSpace :
|
||||
@ -45,6 +45,7 @@ struct CUT5_API FTrackData
|
||||
FSlateBrush Brush;
|
||||
int32 TrackNum = 1;
|
||||
TArray<FClipData> ClipData;
|
||||
|
||||
};
|
||||
|
||||
struct CUT5_API FClipData
|
||||
@ -98,6 +99,8 @@ struct CUT5_API FClipData
|
||||
FString PlayerName;
|
||||
TArray<FColor> PlayerLightData;
|
||||
|
||||
// Sound
|
||||
TObjectPtr<USoundWave> Sound;
|
||||
|
||||
};
|
||||
struct CUT5_API FTimelinePropertyData
|
||||
@ -114,6 +117,11 @@ struct CUT5_API FTimelinePropertyData
|
||||
// Movie Data
|
||||
FString MoviePath = "";
|
||||
int32 MovieFrameLength = 0;
|
||||
FString IconPath;
|
||||
|
||||
// AudioData
|
||||
TObjectPtr<USoundWave> Sound;
|
||||
|
||||
};
|
||||
|
||||
class CUT5_API FCutDragDropBase : public FDecoratedDragDropOp
|
||||
|
@ -14,6 +14,7 @@
|
||||
#undef check
|
||||
#include <opencv2/videoio.hpp>
|
||||
|
||||
#include "RuntimeAudioImporterLibrary.h"
|
||||
#include "Cut5/Utils/OpencvUtils.h"
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
@ -59,8 +60,45 @@ void SCustomInputPanel::Construct(const FArguments& InArgs)
|
||||
|
||||
for (int32 i = 0; i < OpenFileName.Num(); i++)
|
||||
{
|
||||
if (FPaths::GetExtension(OpenFileName[i]) == "mp3")
|
||||
{
|
||||
URuntimeAudioImporterLibrary* AudioImporter = URuntimeAudioImporterLibrary::CreateRuntimeAudioImporter();
|
||||
AudioImporter->ImportAudioFromFile(OpenFileName[i], ERuntimeAudioFormat::Auto);
|
||||
AudioImporter->OnResultNoDynamic.AddLambda([&, OpenFileName, i](URuntimeAudioImporterLibrary* Importer,
|
||||
UImportedSoundWave* ImportedSoundWave, ERuntimeImportStatus Status)
|
||||
{
|
||||
switch (Status)
|
||||
{
|
||||
case ERuntimeImportStatus::SuccessfulImport:
|
||||
{
|
||||
FTimelinePropertyData PropertyData;
|
||||
PropertyData.Name = OpenFileName[i];
|
||||
PropertyData.Type = ETrackType::AudioTrack;
|
||||
PropertyData.MoviePath = OpenFileName[i];
|
||||
PropertyData.Sound = ImportedSoundWave;
|
||||
PropertyData.MovieFrameLength = ImportedSoundWave->GetDuration() * FGlobalData::GlobalFPS;
|
||||
|
||||
GridPanel->AddSlot(GridPanel->GetChildren()->Num() % 3, GridPanel->GetChildren()->Num() / 3)
|
||||
[
|
||||
SNew(SCustomInputResource)
|
||||
.PropertyData(PropertyData)
|
||||
];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
return FReply::Handled();
|
||||
}
|
||||
Async(EAsyncExecution::Thread, [&, this, OpenFileName, i]
|
||||
{
|
||||
|
||||
if (FPaths::GetExtension(OpenFileName[i]) != "mp4")
|
||||
{
|
||||
|
||||
}
|
||||
FString IconPath = FOpencvUtils::GetVideoFrameIconName(OpenFileName[i]);
|
||||
cv::VideoCapture NewCapture(TCHAR_TO_UTF8(*OpenFileName[i]));
|
||||
const int32 FrameCount = NewCapture.get(cv::CAP_PROP_FRAME_COUNT);
|
||||
|
||||
@ -71,6 +109,7 @@ void SCustomInputPanel::Construct(const FArguments& InArgs)
|
||||
PropertyData.Type = ETrackType::VideoTrack;
|
||||
PropertyData.MoviePath = OpenFileName[i];
|
||||
PropertyData.MovieFrameLength = FrameCount;
|
||||
PropertyData.IconPath = IconPath;
|
||||
GridPanel->AddSlot(GridPanel->GetChildren()->Num() % 3, GridPanel->GetChildren()->Num() / 3)
|
||||
[
|
||||
SNew(SCustomInputResource)
|
||||
@ -98,4 +137,5 @@ FReply SCustomInputPanel::OnDrop(const FGeometry& MyGeometry, const FDragDropEve
|
||||
return FReply::Handled().EndDragDrop();
|
||||
}
|
||||
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "RuntimeAudioImporterLibrary.h"
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
#include "Widgets/Layout/SGridPanel.h"
|
||||
|
||||
@ -22,6 +23,7 @@ public:
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override;
|
||||
|
||||
|
||||
TSharedPtr<SGridPanel> GridPanel;
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ void SCustomInputResource::Construct(const FArguments& InArgs)
|
||||
{
|
||||
PropertyData = InArgs._PropertyData;
|
||||
VideoCapture = InArgs._VideoCapture;
|
||||
FSlateDynamicImageBrush* SlateDynamicImageBrush = new FSlateDynamicImageBrush(*PropertyData.IconPath, FVector2D(600, 360));
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SBox)
|
||||
@ -29,9 +30,8 @@ void SCustomInputResource::Construct(const FArguments& InArgs)
|
||||
.HAlign(HAlign_Fill)
|
||||
.VAlign(VAlign_Fill)
|
||||
[
|
||||
SNew(SImage)
|
||||
SNew(SImage).Image(SlateDynamicImageBrush)
|
||||
]
|
||||
|
||||
]
|
||||
];
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "STimelinePropertyPanel.h"
|
||||
#include "STrackBody.h"
|
||||
#include "STrackHead.h"
|
||||
#include "Components/AudioComponent.h"
|
||||
#include "Widgets/Layout/SConstraintCanvas.h"
|
||||
#include "Widgets/Layout/SScaleBox.h"
|
||||
#include "Widgets/Views/STreeView.h"
|
||||
@ -205,6 +206,24 @@ void SCutMainWindow::OnAddNewTrack(ETrackType Type)
|
||||
}
|
||||
}
|
||||
|
||||
TObjectPtr<UAudioComponent> SCutMainWindow::OnPlaySound(USoundWave* Sound, float StartTime)
|
||||
{
|
||||
UAudioComponent* AudioComponent = NewObject<UAudioComponent>();
|
||||
AudioComponent->SetActive(true);
|
||||
AudioComponent->Activate(true);
|
||||
const TObjectPtr<UAudioComponent> AudioComponentPtr = AudioComponent;
|
||||
AudioComponents.Add(AudioComponentPtr);
|
||||
AudioComponent->SetSound(Sound);
|
||||
AudioComponent->Play(StartTime);
|
||||
return AudioComponentPtr;
|
||||
}
|
||||
|
||||
void SCutMainWindow::OnStopSound(TObjectPtr<UAudioComponent> Component)
|
||||
{
|
||||
Component->Stop();
|
||||
AudioComponents.Remove(Component);
|
||||
}
|
||||
|
||||
FString SCutMainWindow::GetGroupName(TSharedPtr<IWidgetInterface> WidgetInterface)
|
||||
{
|
||||
for (FSingleTrackGroupInstance& Instance : CutTimeline->TrackGroupInstances)
|
||||
|
@ -45,5 +45,9 @@ public:
|
||||
virtual void OnUpdateLightArray(const TArray<FColor>& LightArray) override;
|
||||
virtual void OnUpdatePlayers(TSharedPtr<class IWidgetInterface> TrackBody, FColor PlayerColor) override;
|
||||
virtual void OnAddNewTrack(ETrackType Type) override;
|
||||
virtual TObjectPtr<UAudioComponent> OnPlaySound(USoundWave* Sound, float StartTime) override;
|
||||
virtual void OnStopSound(TObjectPtr<UAudioComponent> Component) override;
|
||||
TArray<TObjectPtr<UAudioComponent>> AudioComponents;
|
||||
|
||||
virtual FString GetGroupName(TSharedPtr<IWidgetInterface> WidgetInterface) override;
|
||||
};
|
||||
|
@ -222,8 +222,10 @@ void SCutTimeline::Construct(const FArguments& InArgs)
|
||||
];
|
||||
TrackHeadScrollBox->SetScrollBarVisibility(EVisibility::Hidden);
|
||||
|
||||
FTrackData AudioData(TEXT("音频"), ETrackType::AudioTrack);
|
||||
AddNewTrackToGroup(TEXT("固定轨道"), AudioData);
|
||||
FTrackData AudioDataL(TEXT("音频L"), ETrackType::AudioTrack);
|
||||
AddNewTrackToGroup(TEXT("固定轨道"), AudioDataL);
|
||||
FTrackData AudioDataR(TEXT("音频R"), ETrackType::AudioTrack);
|
||||
AddNewTrackToGroup(TEXT("固定轨道"), AudioDataR);
|
||||
FTrackData ProjectorData(TEXT("投影仪"), ETrackType::ProjectorTrack);
|
||||
AddNewTrackToGroup(TEXT("固定轨道"), ProjectorData);
|
||||
FTrackData VideoData(TEXT("视频"), ETrackType::VideoTrack);
|
||||
|
@ -4,9 +4,11 @@
|
||||
#include "STimelineClip.h"
|
||||
|
||||
#include "SlateOptMacros.h"
|
||||
#include "Components/AudioComponent.h"
|
||||
#include "Cut5/WidgetInterface.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/Texture2D.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
@ -53,7 +55,11 @@ void STimelineClip::Construct(const FArguments& InArgs)
|
||||
|
||||
]
|
||||
];
|
||||
|
||||
if (ClipData->ClipType == ETrackType::AudioTrack)
|
||||
{
|
||||
AudioComponent = NewObject<UAudioComponent>();
|
||||
AudioComponent->SetSound(ClipData->Sound);
|
||||
}
|
||||
}
|
||||
|
||||
void STimelineClip::Seek(int32 Frame)
|
||||
@ -90,7 +96,6 @@ void STimelineClip::Seek(int32 Frame)
|
||||
}
|
||||
}
|
||||
LastSeekFrame = SeekMovieFrame;
|
||||
GEngine->AddOnScreenDebugMessage(-1, 10.0F, FColor::Red, FString::Printf(TEXT("Read Time: %f"), (FDateTime::Now() - A).GetTotalMilliseconds()));
|
||||
|
||||
cv::Mat Read;
|
||||
ClipData->VideoCapture->retrieve(Read);
|
||||
@ -108,7 +113,6 @@ void STimelineClip::Seek(int32 Frame)
|
||||
RGBAData[i * 4 + 2] = Read.data[i * 3 + 2];
|
||||
RGBAData[i * 4 + 3] = 255;
|
||||
}
|
||||
GEngine->AddOnScreenDebugMessage(-1, 10.0F, FColor::Red, FString::Printf(TEXT("RGBA Time: %f"), (FDateTime::Now() - A).GetTotalMilliseconds()));
|
||||
void* MipData = Texture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
|
||||
FMemory::Memcpy(MipData, RGBAData, Read.cols * Read.rows * 4);
|
||||
Texture->GetPlatformData()->Mips[0].BulkData.Unlock();
|
||||
@ -140,6 +144,30 @@ void STimelineClip::Seek(int32 Frame)
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ETrackType::AudioTrack:
|
||||
{
|
||||
const int32 Offset = Frame - (ClipData->ClipStartTime / FGlobalData::DefaultTimeTickSpace);
|
||||
const int32 SeekMovieFrame = ClipData->VideoStartFrame + Offset;
|
||||
if (bIsPlaying == true)
|
||||
{
|
||||
if (SeekMovieFrame >= ClipData->VideoEndFrame)
|
||||
{
|
||||
MainWidgetInterface->OnStopSound(AudioComponent);
|
||||
AudioComponent = nullptr;
|
||||
bIsPlaying = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioComponent = MainWidgetInterface->OnPlaySound(ClipData->Sound, static_cast<float>(SeekMovieFrame) / 30.0f);
|
||||
bIsPlaying = true;
|
||||
}
|
||||
|
||||
// 在UE中如何操作声音设备?
|
||||
//
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -39,5 +39,9 @@ public:
|
||||
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
TSharedPtr<IWidgetInterface> Body;
|
||||
int32 LastSeekFrame = 0;
|
||||
|
||||
bool bIsPlaying = false;
|
||||
// Sound
|
||||
TObjectPtr<UAudioComponent> AudioComponent;
|
||||
};
|
||||
|
||||
|
@ -91,6 +91,7 @@ FReply STrackBody::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& Dra
|
||||
NewClipData.ClipType = ClipDragOperation.TimelinePropertyData.Type;
|
||||
NewClipData.ClipStartTime = FMath::TruncToInt(MyGeometry.AbsoluteToLocal(DragDropEvent.GetScreenSpacePosition()).X / FGlobalData::DefaultTimeTickSpace) * FGlobalData::DefaultTimeTickSpace;
|
||||
NewClipData.ClipColors.Add(FLinearColor(1, 1, 1, 1));
|
||||
// 对拖拽物进行不同的操作
|
||||
if (ClipDragOperation.TimelinePropertyData.Type == ETrackType::VideoTrack)
|
||||
{
|
||||
// 如果拖拽物是视频,那么对不同轨道进行不同和操作
|
||||
@ -109,12 +110,19 @@ FReply STrackBody::OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& Dra
|
||||
NewClipData.PlayerLightData = FOpencvUtils::GetVideoSingleLightColor(ClipDragOperation.TimelinePropertyData.MoviePath);
|
||||
}
|
||||
}
|
||||
else if (ClipDragOperation.TimelinePropertyData.Type == ETrackType::LightArrayTrack)
|
||||
// 如果拖拽物是音频
|
||||
else if (ClipDragOperation.TimelinePropertyData.Type == ETrackType::AudioTrack)
|
||||
{
|
||||
NewClipData.Sound = ClipDragOperation.TimelinePropertyData.Sound;
|
||||
NewClipData.ClipEndTime = NewClipData.ClipStartTime + ClipDragOperation.TimelinePropertyData.MovieFrameLength * FGlobalData::DefaultTimeTickSpace;
|
||||
NewClipData.VideoEndFrame = ClipDragOperation.TimelinePropertyData.MovieFrameLength;
|
||||
}
|
||||
else if (ClipDragOperation.TimelinePropertyData.Type == ETrackType::PlayerTrack)
|
||||
{
|
||||
NewClipData.ClipEndTime = NewClipData.ClipStartTime + 200;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Overwrite the clip if it is in the same position
|
||||
for (int32 i = TrackHead->TrackData.ClipData.Num() - 1; i >= 0; i--)
|
||||
|
BIN
Temp/973F81CF4C990C5F6172D19562DC4CE2.png
Normal file
BIN
Temp/973F81CF4C990C5F6172D19562DC4CE2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 114 KiB |
BIN
Temp/A7F104E44C6D332967FCF59024DA15D0.png
Normal file
BIN
Temp/A7F104E44C6D332967FCF59024DA15D0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 114 KiB |
BIN
Temp/B1F5BF2148B5A9FE7CB82BAE711CC5CB
Normal file
BIN
Temp/B1F5BF2148B5A9FE7CB82BAE711CC5CB
Normal file
Binary file not shown.
After Width: | Height: | Size: 195 KiB |
BIN
Temp/DDB7B76E4E3D9044E0FDB4A5788982F7.png
Normal file
BIN
Temp/DDB7B76E4E3D9044E0FDB4A5788982F7.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 297 KiB |
BIN
Temp/DEBFE51B416C3ACB99F045876844E347.png
Normal file
BIN
Temp/DEBFE51B416C3ACB99F045876844E347.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 189 KiB |
Loading…
Reference in New Issue
Block a user