检查卡顿问题

This commit is contained in:
Sch 2023-07-12 15:40:47 +08:00
parent 7112e49fbe
commit 6b02499289
64 changed files with 32360 additions and 33 deletions

View File

@ -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

View File

@ -24,6 +24,15 @@
{
"Name": "OpenCV",
"Enabled": true
},
{
"Name": "SoundUtilities",
"Enabled": true
},
{
"Name": "RuntimeAudioImporter",
"Enabled": true,
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View 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"
}
]
}

View File

@ -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
}

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,9 @@
// Georgy Treshchev 2023.
#include "PreImportedSoundAsset.h"
UPreImportedSoundAsset::UPreImportedSoundAsset()
: AudioFormat(ERuntimeAudioFormat::Mp3)
{
}

View File

@ -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);

View File

@ -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
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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
};

View File

@ -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;
}
};

View File

@ -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
};

View File

@ -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
};

View File

@ -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;
}
};

View File

@ -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);
};

View File

@ -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
};

View File

@ -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
};

View File

@ -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

View File

@ -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
};

View File

@ -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;
};

View File

@ -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);

View File

@ -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);
};

View File

@ -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;
};

View File

@ -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
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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")));
}
}

View File

@ -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

View File

@ -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)

View File

@ -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.
};

View File

@ -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;
};

View File

@ -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"
}
);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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");

View File

@ -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(); };

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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

View File

@ -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

View File

@ -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;
};

View File

@ -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)
]
]
];

View File

@ -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)

View File

@ -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;
};

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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--)

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB