start 4.26

This commit is contained in:
Georgy Treshchev
2021-03-05 09:40:51 +01:00
commit 5c885db771
39 changed files with 26001 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28315.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine", "Engine", "{94A6C6F3-99B3-346E-9557-ABF9D4064DBD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Games", "Games", "{8E2F6A87-1826-34F4-940C-CC23A48F9FE4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AudioImporterTest", "Intermediate\ProjectFiles\AudioImporterTest.vcxproj", "{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UE4", "Intermediate\ProjectFiles\UE4.vcxproj", "{41DC5BBD-56BC-4896-8D8D-5C13F4636397}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Visualizers", "Visualizers", "{1CCEC849-CC72-4C59-8C36-2F7C38706D4C}"
ProjectSection(SolutionItems) = preProject
D:\UE4\UE_4.26\Engine\Extras\VisualStudioDebugging\UE4.natvis = D:\UE4\UE_4.26\Engine\Extras\VisualStudioDebugging\UE4.natvis
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
DebugGame Editor|Android = DebugGame Editor|Android
DebugGame Editor|Win32 = DebugGame Editor|Win32
DebugGame Editor|Win64 = DebugGame Editor|Win64
DebugGame|Android = DebugGame|Android
DebugGame|Win32 = DebugGame|Win32
DebugGame|Win64 = DebugGame|Win64
Development Editor|Android = Development Editor|Android
Development Editor|Win32 = Development Editor|Win32
Development Editor|Win64 = Development Editor|Win64
Development|Android = Development|Android
Development|Win32 = Development|Win32
Development|Win64 = Development|Win64
Shipping|Android = Shipping|Android
Shipping|Win32 = Shipping|Win32
Shipping|Win64 = Shipping|Win64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame Editor|Android.ActiveCfg = Invalid|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame Editor|Win32.ActiveCfg = Invalid|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame Editor|Win64.ActiveCfg = DebugGame_Editor|x64
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame Editor|Win64.Build.0 = DebugGame_Editor|x64
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Android.ActiveCfg = Android_DebugGame|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Android.Build.0 = Android_DebugGame|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Win32.ActiveCfg = DebugGame|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Win32.Build.0 = DebugGame|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Win64.ActiveCfg = DebugGame|x64
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.DebugGame|Win64.Build.0 = DebugGame|x64
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development Editor|Android.ActiveCfg = Invalid|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development Editor|Win32.ActiveCfg = Invalid|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development Editor|Win64.ActiveCfg = Development_Editor|x64
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development Editor|Win64.Build.0 = Development_Editor|x64
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Android.ActiveCfg = Android_Development|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Android.Build.0 = Android_Development|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Win32.ActiveCfg = Development|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Win32.Build.0 = Development|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Win64.ActiveCfg = Development|x64
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Development|Win64.Build.0 = Development|x64
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Android.ActiveCfg = Android_Shipping|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Android.Build.0 = Android_Shipping|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Win32.ActiveCfg = Shipping|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Win32.Build.0 = Shipping|Win32
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Win64.ActiveCfg = Shipping|x64
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F}.Shipping|Win64.Build.0 = Shipping|x64
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame Editor|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame Editor|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.DebugGame|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development Editor|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development Editor|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development Editor|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Development|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Shipping|Android.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Shipping|Win32.ActiveCfg = BuiltWithUnrealBuildTool|Win32
{41DC5BBD-56BC-4896-8D8D-5C13F4636397}.Shipping|Win64.ActiveCfg = BuiltWithUnrealBuildTool|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{41DC5BBD-56BC-4896-8D8D-5C13F4636397} = {94A6C6F3-99B3-346E-9557-ABF9D4064DBD}
{05EAA636-D8E0-4E88-B98A-0396DD9D9A1F} = {8E2F6A87-1826-34F4-940C-CC23A48F9FE4}
EndGlobalSection
EndGlobal
+59
View File
@@ -0,0 +1,59 @@
{
"FileVersion": 3,
"EngineAssociation": "4.26",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "AudioImporterTest",
"Type": "Runtime",
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "RuntimeAudioImporter",
"Enabled": true,
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036"
},
{
"Name": "AndroidMedia",
"Enabled": false
},
{
"Name": "AndroidMoviePlayer",
"Enabled": false
},
{
"Name": "AvfMedia",
"Enabled": false
},
{
"Name": "MediaCompositing",
"Enabled": false
},
{
"Name": "ImgMedia",
"Enabled": false
},
{
"Name": "WindowsMoviePlayer",
"Enabled": false
},
{
"Name": "MediaPlayerEditor",
"Enabled": false
},
{
"Name": "MagicLeapMedia",
"Enabled": false,
"SupportedTargetPlatforms": [
"Lumin"
]
},
{
"Name": "WmfMedia",
"Enabled": false
}
]
}
View File
+146
View File
@@ -0,0 +1,146 @@
[/Script/EngineSettings.GameMapsSettings]
EditorStartupMap=/Game/MainLevelTest.MainLevelTest
LocalMapOptions=
TransitionMap=None
bUseSplitscreen=False
TwoPlayerSplitscreenLayout=Horizontal
ThreePlayerSplitscreenLayout=FavorTop
FourPlayerSplitscreenLayout=Grid
bOffsetPlayerGamepadIds=False
GameInstanceClass=/Script/Engine.GameInstance
GameDefaultMap=/Game/MainLevelTest.MainLevelTest
ServerDefaultMap=/Engine/Maps/Entry.Entry
GlobalDefaultGameMode=/Script/Engine.GameModeBase
GlobalDefaultServerGameMode=None
[/Script/HardwareTargeting.HardwareTargetingSettings]
TargetedHardwareClass=Mobile
AppliedTargetedHardwareClass=Mobile
DefaultGraphicsPerformance=Maximum
AppliedDefaultGraphicsPerformance=Maximum
[/Script/Engine.Engine]
+ActiveGameNameRedirects=(OldGameName="TP_Blank",NewGameName="/Script/AudioImporterTest")
+ActiveGameNameRedirects=(OldGameName="/Script/TP_Blank",NewGameName="/Script/AudioImporterTest")
+ActiveClassRedirects=(OldClassName="TP_BlankGameModeBase",NewClassName="AudioImporterTestGameModeBase")
[/Script/Engine.RendererSettings]
r.Mobile.DisableVertexFog=True
r.Shadow.CSM.MaxMobileCascades=2
r.MobileMSAA=1
r.Mobile.AllowDitheredLODTransition=False
r.Mobile.AllowSoftwareOcclusion=False
r.Mobile.VirtualTextures=False
r.DiscardUnusedQuality=False
r.AllowOcclusionQueries=True
r.MinScreenRadiusForLights=0.030000
r.MinScreenRadiusForDepthPrepass=0.030000
r.MinScreenRadiusForCSMDepth=0.010000
r.PrecomputedVisibilityWarning=False
r.TextureStreaming=True
Compat.UseDXT5NormalMaps=False
r.VirtualTextures=False
r.VirtualTexturedLightmaps=False
r.VT.TileSize=128
r.VT.TileBorderSize=4
r.vt.FeedbackFactor=16
r.VT.EnableCompressZlib=True
r.VT.EnableCompressCrunch=False
r.ClearCoatNormal=False
r.ReflectionCaptureResolution=128
r.Mobile.ReflectionCaptureCompression=False
r.ReflectionEnvironmentLightmapMixBasedOnRoughness=True
r.ForwardShading=False
r.VertexFoggingForOpaque=True
r.AllowStaticLighting=True
r.NormalMapsForStaticLighting=False
r.GenerateMeshDistanceFields=False
r.DistanceFieldBuild.EightBit=False
r.GenerateLandscapeGIData=False
r.DistanceFieldBuild.Compress=False
r.TessellationAdaptivePixelsPerTriangle=48.000000
r.SeparateTranslucency=False
r.TranslucentSortPolicy=0
TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000)
r.CustomDepth=1
r.CustomDepthTemporalAAJitter=True
r.PostProcessing.PropagateAlpha=0
r.DefaultFeature.Bloom=True
r.DefaultFeature.AmbientOcclusion=False
r.DefaultFeature.AmbientOcclusionStaticFraction=True
r.DefaultFeature.AutoExposure=False
r.DefaultFeature.AutoExposure.Method=0
r.DefaultFeature.AutoExposure.Bias=1.000000
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=False
r.UsePreExposure=True
r.EyeAdaptation.EditorOnly=False
r.DefaultFeature.MotionBlur=False
r.DefaultFeature.LensFlare=False
r.TemporalAA.Upsampling=False
r.SSGI.Enable=False
r.DefaultFeature.AntiAliasing=0
r.DefaultFeature.LightUnits=1
r.DefaultBackBufferPixelFormat=4
r.Shadow.UnbuiltPreviewInGame=True
r.StencilForLODDither=False
r.EarlyZPass=3
r.EarlyZPassOnlyMaterialMasking=False
r.DBuffer=True
r.ClearSceneMethod=1
r.BasePassOutputsVelocity=False
r.VertexDeformationOutputsVelocity=False
r.SelectiveBasePassOutputs=False
bDefaultParticleCutouts=False
fx.GPUSimulationTextureSizeX=1024
fx.GPUSimulationTextureSizeY=1024
r.AllowGlobalClipPlane=False
r.GBufferFormat=1
r.MorphTarget.Mode=True
r.GPUCrashDebugging=False
vr.InstancedStereo=False
r.MobileHDR=True
vr.MobileMultiView=False
r.Mobile.UseHWsRGBEncoding=False
vr.RoundRobinOcclusion=False
vr.ODSCapture=False
r.MeshStreaming=False
r.WireframeCullThreshold=5.000000
r.RayTracing=False
r.RayTracing.UseTextureLod=False
r.SupportStationarySkylight=True
r.SupportLowQualityLightmaps=True
r.SupportPointLightWholeSceneShadows=True
r.SupportAtmosphericFog=True
r.SupportSkyAtmosphere=True
r.SupportSkyAtmosphereAffectsHeightFog=False
r.SkinCache.CompileShaders=False
r.SkinCache.DefaultBehavior=1
r.SkinCache.SceneMemoryLimitInMB=128.000000
r.Mobile.EnableStaticAndCSMShadowReceivers=True
r.Mobile.EnableMovableLightCSMShaderCulling=True
r.Mobile.AllowDistanceFieldShadows=True
r.Mobile.AllowMovableDirectionalLights=True
r.MobileNumDynamicPointLights=4
r.MobileDynamicPointLightsUseStaticBranch=True
r.Mobile.EnableMovableSpotlights=False
r.Mobile.EnableMovableSpotlightsShadow=False
r.GPUSkin.Support16BitBoneIndex=False
r.GPUSkin.Limit2BoneInfluences=False
r.SupportDepthOnlyIndexBuffers=True
r.SupportReversedIndexBuffers=True
r.LightPropagationVolume=False
r.Mobile.AmbientOcclusion=False
r.GPUSkin.UnlimitedBoneInfluences=False
r.GPUSkin.UnlimitedBoneInfluencesThreshold=8
r.Mobile.PlanarReflectionMode=0
bStreamSkeletalMeshLODs=(Default=False,PerPlatform=())
bDiscardSkeletalMeshOptionalLODs=(Default=False,PerPlatform=())
VisualizeCalibrationColorMaterialPath=None
VisualizeCalibrationCustomMaterialPath=None
VisualizeCalibrationGrayscaleMaterialPath=None
[/Script/Slate.SlateSettings]
bExplicitCanvasChildZOrder=True
+3
View File
@@ -0,0 +1,3 @@
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=E5F8CB474977D102BC3EC9B00A5B7D1C
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
git config --global user.email "gtreshchev@gmail.com"
git config --global user.name "Georgy Treshchev"
git add *
git commit -m "start 4.26"
git push -f -u origin test
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1,43 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "RuntimeAudioImporter",
"Description": "Runtime Audio Importer plugin for Unreal Engine 4. Supports mp3, wav and flac formats.",
"Category": "Audio",
"CreatedBy": "Georgy Treshchev",
"CreatedByURL": "https://github.com/Respirant/RuntimeAudioImporter",
"DocsURL": "https://unreal.blog/runtime-audio-importer",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/81fd278e318e4235a028d3fdf46bf036",
"SupportURL": "https://t.me/unrealblog",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": true,
"EngineVersion": "4.26.0",
"Modules": [
{
"Name": "RuntimeAudioImporter",
"Type": "Runtime",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64",
"Win32",
"Mac",
"Android",
"IOS",
"Linux"
],
"BlacklistPlatforms": [
"XboxOne",
"PS4",
"TVOS",
"Switch"
]
},
{
"Name": "RuntimeAudioImporterEditor",
"Type": "Editor"
}
]
}
@@ -0,0 +1,346 @@
// Georgy Treshchev 2021.
#include "AdvancedPCMCompressor.h"
#include "Interfaces/IAudioFormat.h"
#include "Serialization/MemoryWriter.h"
#include "ADPCMAudioInfo.h"
#define FORMAT_FOURCC(ch0, ch1, ch2, ch3)\
((uint32)(uint8)(ch0) | ((uint32)(uint8)(ch1) << 8) |\
((uint32)(uint8)(ch2) << 16) | ((uint32)(uint8)(ch3) << 24 ))
namespace BaseDataOperations
{
template <uint32 N>
void GenerateWaveFile(RiffDataChunk (& RiffDataChunks)[N], TArray<uint8>& CompressedDataStore)
{
uint32 RiffDataSize = sizeof(uint32);
for (uint32 Scan = 0; Scan < N; ++Scan)
{
const RiffDataChunk& Chunk = RiffDataChunks[Scan];
RiffDataSize += (sizeof(Chunk.ID) + sizeof(Chunk.DataSize) + Chunk.DataSize);
}
const uint32 OutputDataSize = RiffDataSize + sizeof(uint32) + sizeof(uint32);
CompressedDataStore.Empty(OutputDataSize);
FMemoryWriter CompressedData(CompressedDataStore);
uint32 rID = FORMAT_FOURCC('R', 'I', 'F', 'F');
CompressedData.Serialize(&rID, sizeof(rID));
CompressedData.Serialize(&RiffDataSize, sizeof(RiffDataSize));
rID = FORMAT_FOURCC('W', 'A', 'V', 'E');
CompressedData.Serialize(&rID, sizeof(rID));
for (uint32 Scan = 0; Scan < N; ++Scan)
{
RiffDataChunk& Chunk = RiffDataChunks[Scan];
CompressedData.Serialize(&Chunk.ID, sizeof(Chunk.ID));
CompressedData.Serialize(&Chunk.DataSize, sizeof(Chunk.DataSize));
CompressedData.Serialize(Chunk.Data, Chunk.DataSize);
}
}
template <typename T, uint32 B>
T SignExtend(const T ValueToExtend)
{
struct
{
T ExtendedValue:B;
} SignExtender;
return SignExtender.ExtendedValue = ValueToExtend;
}
template <typename T>
T ReadFromByteStream(const uint8* ByteStream, int32& ReadIndex, bool bLittleEndian = true)
{
T ValueRaw = 0;
if (bLittleEndian)
{
#if PLATFORM_LITTLE_ENDIAN
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
#else
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
#endif // PLATFORM_LITTLE_ENDIAN
{
ValueRaw |= ByteStream[ReadIndex++] << 8 * ByteIndex;
}
}
else
{
#if PLATFORM_LITTLE_ENDIAN
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
#else
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
#endif // PLATFORM_LITTLE_ENDIAN
{
ValueRaw |= ByteStream[ReadIndex++] << 8 * ByteIndex;
}
}
return ValueRaw;
}
template <typename T>
void WriteToByteStream(T Value, uint8* ByteStream, int32& WriteIndex, bool bLittleEndian = true)
{
if (bLittleEndian)
{
#if PLATFORM_LITTLE_ENDIAN
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
#else
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
#endif // PLATFORM_LITTLE_ENDIAN
{
ByteStream[WriteIndex++] = (Value >> (8 * ByteIndex)) & 0xFF;
}
}
else
{
#if PLATFORM_LITTLE_ENDIAN
for (int32 ByteIndex = sizeof(T) - 1; ByteIndex >= 0; --ByteIndex)
#else
for (int32 ByteIndex = 0; ByteIndex < sizeof(T); ++ByteIndex)
#endif // PLATFORM_LITTLE_ENDIAN
{
ByteStream[WriteIndex++] = (Value >> (8 * ByteIndex)) & 0xFF;
}
}
}
template <typename T>
T ReadFromArray(const T* ElementArray, int32& ReadIndex, int32 NumElements, int32 IndexStride = 1)
{
T OutputValue = 0;
if (ReadIndex >= 0 && ReadIndex < NumElements)
{
OutputValue = ElementArray[ReadIndex];
ReadIndex += IndexStride;
}
return OutputValue;
}
} // end namespace
namespace LPCM
{
void GenerateCompressedData(const TArray<uint8>& InputPCMData, TArray<uint8>& CompressedDataStore,
const FSoundQualityInfo& QualityInfo)
{
WaveFormatHeader Format;
Format.nChannels = static_cast<uint16>(QualityInfo.NumChannels);
Format.nSamplesPerSec = QualityInfo.SampleRate;
Format.nBlockAlign = static_cast<uint16>(Format.nChannels * sizeof(int16));
Format.nAvgBytesPerSec = Format.nBlockAlign * QualityInfo.SampleRate;
Format.wBitsPerSample = 16;
Format.wFormatTag = WAVE_FORMAT_LPCM;
RiffDataChunk RiffDataChunks[2];
RiffDataChunks[0].ID = FORMAT_FOURCC('f', 'm', 't', ' ');
RiffDataChunks[0].DataSize = sizeof(Format);
RiffDataChunks[0].Data = reinterpret_cast<uint8*>(&Format);
RiffDataChunks[1].ID = FORMAT_FOURCC('d', 'a', 't', 'a');
RiffDataChunks[1].DataSize = InputPCMData.Num();
RiffDataChunks[1].Data = const_cast<uint8*>(InputPCMData.GetData());
BaseDataOperations::GenerateWaveFile(RiffDataChunks, CompressedDataStore);
}
} // end namespace LPCM
namespace ADPCM
{
template <typename T>
static void GetAdaptationTable(T (& OutAdaptationTable)[NUM_ADAPTATION_TABLE])
{
static T AdaptationTable[] =
{
230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230
};
FMemory::Memcpy(&OutAdaptationTable, AdaptationTable, sizeof(AdaptationTable));
}
struct FAdaptationContext
{
int32 AdaptationTable[NUM_ADAPTATION_TABLE];
int32 AdaptationCoefficient1[NUM_ADAPTATION_COEFF];
int32 AdaptationCoefficient2[NUM_ADAPTATION_COEFF];
int32 AdaptationDelta;
int32 Coefficient1;
int32 Coefficient2;
int32 Sample1;
int32 Sample2;
FAdaptationContext() :
AdaptationDelta(0),
Coefficient1(0),
Coefficient2(0),
Sample1(0),
Sample2(0)
{
GetAdaptationTable(AdaptationTable);
GetAdaptationCoefficients(AdaptationCoefficient1, AdaptationCoefficient2);
}
};
uint8 EncodeNibble(FAdaptationContext& Context, int16 NextSample)
{
int32 PredictedSample = (Context.Sample1 * Context.Coefficient1 + Context.Sample2 * Context.Coefficient2) / 256;
int32 ErrorDelta = (NextSample - PredictedSample) / Context.AdaptationDelta;
ErrorDelta = FMath::Clamp(ErrorDelta, -8, 7);
PredictedSample += (Context.AdaptationDelta * ErrorDelta);
PredictedSample = FMath::Clamp(PredictedSample, -32768, 32767);
int8 SmallDelta = static_cast<int8>(ErrorDelta);
const uint8 EncodedNibble = reinterpret_cast<uint8&>(SmallDelta) & 0x0F;
Context.Sample2 = Context.Sample1;
Context.Sample1 = static_cast<int16>(PredictedSample);
Context.AdaptationDelta = (Context.AdaptationDelta * Context.AdaptationTable[EncodedNibble]) / 256;
Context.AdaptationDelta = FMath::Max(Context.AdaptationDelta, 16);
return EncodedNibble;
}
int32 EncodeBlock(const int16* InputPCMSamples, int32 SampleStride, int32 NumSamples, int32 BlockSize,
uint8* EncodedADPCMData)
{
FAdaptationContext Context;
int32 ReadIndex = 0;
int32 WriteIndex = 0;
const uint8 CoefficientIndex = 0;
Context.AdaptationDelta = Context.AdaptationTable[0];
Context.Sample2 = BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride);
Context.Sample1 = BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride);
Context.Coefficient1 = Context.AdaptationCoefficient1[CoefficientIndex];
Context.Coefficient2 = Context.AdaptationCoefficient2[CoefficientIndex];
BaseDataOperations::WriteToByteStream<uint8>(CoefficientIndex, EncodedADPCMData, WriteIndex);
BaseDataOperations::WriteToByteStream<int16>(Context.AdaptationDelta, EncodedADPCMData, WriteIndex);
BaseDataOperations::WriteToByteStream<int16>(Context.Sample1, EncodedADPCMData, WriteIndex);
BaseDataOperations::WriteToByteStream<int16>(Context.Sample2, EncodedADPCMData, WriteIndex);
while (WriteIndex < BlockSize)
{
EncodedADPCMData[WriteIndex] = EncodeNibble(
Context, BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride)) << 4;
EncodedADPCMData[WriteIndex++] |= EncodeNibble(
Context, BaseDataOperations::ReadFromArray<int16>(InputPCMSamples, ReadIndex, NumSamples, SampleStride));
}
return WriteIndex;
}
void GenerateCompressedData(const TArray<uint8>& InputPCMData, TArray<uint8>& CompressedDataStore,
const FSoundQualityInfo& QualityInfo)
{
const int32 SourceSampleStride = QualityInfo.NumChannels;
const int32 SourceNumSamples = QualityInfo.SampleDataSize / 2;
const int32 SourceNumSamplesPerChannel = SourceNumSamples / QualityInfo.NumChannels;
const int32 CompressedNumSamplesPerByte = 2;
const int32 PreambleSamples = 2;
const int32 BlockSize = 512;
const int32 PreambleSize = 2 * PreambleSamples + 3;
const int32 CompressedSamplesPerBlock = (BlockSize - PreambleSize) * CompressedNumSamplesPerByte +
PreambleSamples;
const int32 NumBlocksPerChannel = (SourceNumSamplesPerChannel + CompressedSamplesPerBlock - 1) /
CompressedSamplesPerBlock;
const uint32 EncodedADPCMDataSize = NumBlocksPerChannel * BlockSize * QualityInfo.NumChannels;
uint8* EncodedADPCMData = static_cast<uint8*>(FMemory::Malloc(EncodedADPCMDataSize));
FMemory::Memzero(EncodedADPCMData, EncodedADPCMDataSize);
const int16* InputPCMSamples = reinterpret_cast<const int16*>(InputPCMData.GetData());
uint8* EncodedADPCMChannelData = EncodedADPCMData;
for (uint32 ChannelIndex = 0; ChannelIndex < QualityInfo.NumChannels; ++ChannelIndex)
{
const int16* ChannelPCMSamples = InputPCMSamples + ChannelIndex;
int32 SourceSampleOffset = 0;
int32 DestDataOffset = 0;
for (int32 BlockIndex = 0; BlockIndex < NumBlocksPerChannel; ++BlockIndex)
{
EncodeBlock(ChannelPCMSamples + SourceSampleOffset, SourceSampleStride,
SourceNumSamples - SourceSampleOffset, BlockSize, EncodedADPCMChannelData + DestDataOffset);
SourceSampleOffset += CompressedSamplesPerBlock * SourceSampleStride;
DestDataOffset += BlockSize;
}
EncodedADPCMChannelData += DestDataOffset;
}
ADPCMFormatHeader Format;
Format.BaseFormat.nChannels = static_cast<uint16>(QualityInfo.NumChannels);
Format.BaseFormat.nSamplesPerSec = QualityInfo.SampleRate;
Format.BaseFormat.nBlockAlign = static_cast<uint16>(BlockSize);
Format.BaseFormat.wBitsPerSample = 4;
Format.BaseFormat.wFormatTag = WAVE_FORMAT_ADPCM;
Format.wSamplesPerBlock = static_cast<uint16>(CompressedSamplesPerBlock);
Format.BaseFormat.nAvgBytesPerSec = ((Format.BaseFormat.nSamplesPerSec / Format.wSamplesPerBlock) * Format.
BaseFormat.nBlockAlign);
Format.wNumCoef = NUM_ADAPTATION_COEFF;
Format.SamplesPerChannel = SourceNumSamplesPerChannel;
Format.BaseFormat.cbSize = sizeof(Format) - sizeof(Format.BaseFormat);
RiffDataChunk RiffDataChunks[2];
RiffDataChunks[0].ID = FORMAT_FOURCC('f', 'm', 't', ' ');
RiffDataChunks[0].DataSize = sizeof(Format);
RiffDataChunks[0].Data = reinterpret_cast<uint8*>(&Format);
RiffDataChunks[1].ID = FORMAT_FOURCC('d', 'a', 't', 'a');
RiffDataChunks[1].DataSize = EncodedADPCMDataSize;
RiffDataChunks[1].Data = EncodedADPCMData;
BaseDataOperations::GenerateWaveFile(RiffDataChunks, CompressedDataStore);
FMemory::Free(EncodedADPCMData);
}
} // end namespace ADPCM
bool FAdvancedPCMCompressor::GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& OutBuffer)
{
if (QualityInfo.Quality == 100)
{
LPCM::GenerateCompressedData(SrcBuffer, OutBuffer, QualityInfo);
}
else
{
ADPCM::GenerateCompressedData(SrcBuffer, OutBuffer, QualityInfo);
}
return OutBuffer.Num() > 0;
}
void FAdvancedPCMCompressor::AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize)
{
TArray<uint8>& NewBuffer = *new(OutBuffers) TArray<uint8>;
NewBuffer.Empty(ChunkReserveSize);
}
void FAdvancedPCMCompressor::AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData,
int32 ChunkDataSize)
{
TArray<uint8>& TargetBuffer = OutBuffers[OutBuffers.Num() - 1];
TargetBuffer.Append(ChunkData, ChunkDataSize);
}
@@ -0,0 +1,202 @@
// Georgy Treshchev 2021.
#include "OpusCompressor.h"
#include "Interfaces/IAudioFormat.h"
#include "Serialization/MemoryWriter.h"
#include "OpusAudioInfo.h"
#define OUTSIDE_SPEEX
THIRD_PARTY_INCLUDES_START
#include "opus_multistream.h"
#include "speex_resampler.h"
THIRD_PARTY_INCLUDES_END
#define SAMPLE_SIZE ( ( uint32 )sizeof( short ) )
bool FOpusCompressor::GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& CompressedDataStore)
{
const uint16 OpusSampleRate = GetBestOutputSampleRate(QualityInfo.SampleRate);
const int32 OpusFrameSizeMs = 60;
const int32 OpusFrameSizeSamples = (OpusSampleRate * OpusFrameSizeMs) / 1000;
const uint32 SampleStride = SAMPLE_SIZE * QualityInfo.NumChannels;
const int32 BytesPerFrame = OpusFrameSizeSamples * SampleStride;
TArray<uint8> SrcBufferCopy;
if (QualityInfo.SampleRate != OpusSampleRate)
{
if (!ResamplePCM(QualityInfo.NumChannels, SrcBuffer, QualityInfo.SampleRate, SrcBufferCopy, OpusSampleRate))
{
return false;
}
}
else
{
SrcBufferCopy = SrcBuffer;
}
const int32 EncSize = opus_encoder_get_size(QualityInfo.NumChannels);
OpusEncoder* Encoder = static_cast<OpusEncoder*>(FMemory::Malloc(EncSize));
const int32 EncError = opus_encoder_init(Encoder, OpusSampleRate, QualityInfo.NumChannels, OPUS_APPLICATION_AUDIO);
if (EncError != OPUS_OK)
{
FMemory::Free(Encoder);
return false;
}
const int32 BitRate = GetBitRateFromQuality(QualityInfo);
opus_encoder_ctl(Encoder, OPUS_SET_BITRATE(BitRate));
CompressedDataStore.Empty();
FMemoryWriter CompressedData(CompressedDataStore);
int32 SrcBufferOffset = 0;
int32 FramesToEncode = SrcBufferCopy.Num() / BytesPerFrame;
const uint32 AccurateSampleCount = SrcBufferCopy.Num() / SampleStride;
if (SrcBufferCopy.Num() % BytesPerFrame != 0)
{
int32 FrameDiff = BytesPerFrame - (SrcBufferCopy.Num() % BytesPerFrame);
SrcBufferCopy.AddZeroed(FrameDiff);
FramesToEncode++;
}
check(QualityInfo.NumChannels <= MAX_uint8);
check(FramesToEncode <= MAX_uint16);
SerializeHeaderData(CompressedData, OpusSampleRate, AccurateSampleCount, QualityInfo.NumChannels, FramesToEncode);
TArray<uint8> TempCompressedData;
TempCompressedData.AddUninitialized(BytesPerFrame);
while (SrcBufferOffset < SrcBufferCopy.Num())
{
const int32 CompressedDataLength = opus_encode(
Encoder, reinterpret_cast<const opus_int16*>(SrcBufferCopy.GetData() + SrcBufferOffset),
OpusFrameSizeSamples, TempCompressedData.GetData(), TempCompressedData.Num());
if (CompressedDataLength < 0)
{
const char* ErrorStr = opus_strerror(CompressedDataLength);
UE_LOG(LogAudio, Warning, TEXT("Failed to encode: [%d] %s"), CompressedDataLength, ANSI_TO_TCHAR(ErrorStr));
FMemory::Free(Encoder);
CompressedDataStore.Empty();
return false;
}
else
{
check(CompressedDataLength < MAX_uint16);
SerialiseFrameData(CompressedData, TempCompressedData.GetData(), CompressedDataLength);
SrcBufferOffset += BytesPerFrame;
}
}
FMemory::Free(Encoder);
return CompressedDataStore.Num() > 0;
}
uint16 FOpusCompressor::GetBestOutputSampleRate(int32 SampleRate)
{
static const uint16 ValidSampleRates[] =
{
0,
8000,
12000,
16000,
24000,
48000,
};
for (int32 Index = UE_ARRAY_COUNT(ValidSampleRates) - 2; Index >= 0; Index--)
{
if (SampleRate > ValidSampleRates[Index])
{
return ValidSampleRates[Index + 1];
}
}
check(0);
return 0;
}
bool FOpusCompressor::ResamplePCM(uint32 NumChannels, const TArray<uint8>& InBuffer, uint32 InSampleRate,
TArray<uint8>& OutBuffer, uint32 OutSampleRate)
{
int32 err = 0;
SpeexResamplerState* resampler = speex_resampler_init(NumChannels, InSampleRate, OutSampleRate,
SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
if (err != RESAMPLER_ERR_SUCCESS)
{
speex_resampler_destroy(resampler);
return false;
}
const uint32 SampleStride = SAMPLE_SIZE * NumChannels;
const float Duration = (float)InBuffer.Num() / (InSampleRate * SampleStride);
const int32 SafeCopySize = (Duration + 1) * OutSampleRate * SampleStride;
OutBuffer.Empty(SafeCopySize);
OutBuffer.AddUninitialized(SafeCopySize);
uint32 InSamples = InBuffer.Num() / SampleStride;
uint32 OutSamples = OutBuffer.Num() / SampleStride;
if (NumChannels == 1)
{
err = speex_resampler_process_int(resampler, 0, (const short*)(InBuffer.GetData()), &InSamples,
(short*)(OutBuffer.GetData()), &OutSamples);
}
else
{
err = speex_resampler_process_interleaved_int(resampler, (const short*)(InBuffer.GetData()), &InSamples,
(short*)(OutBuffer.GetData()), &OutSamples);
}
speex_resampler_destroy(resampler);
if (err != RESAMPLER_ERR_SUCCESS)
{
return false;
}
const int32 WrittenBytes = (int32)(OutSamples * SampleStride);
if (WrittenBytes < OutBuffer.Num())
{
OutBuffer.SetNum(WrittenBytes, true);
}
return true;
}
int32 FOpusCompressor::GetBitRateFromQuality(FSoundQualityInfo& QualityInfo)
{
const int32 OriginalBitRate = QualityInfo.SampleRate * QualityInfo.NumChannels * SAMPLE_SIZE * 8;
return static_cast<float>(OriginalBitRate) * FMath::GetMappedRangeValueClamped(
FVector2D(1, 100), FVector2D(0.04, 0.25), QualityInfo.Quality);
}
void FOpusCompressor::SerializeHeaderData(FMemoryWriter& CompressedData, uint16 SampleRate, uint32 TrueSampleCount,
uint8 NumChannels, uint16 NumFrames)
{
const char* OpusIdentifier = OPUS_ID_STRING;
CompressedData.Serialize((void*)OpusIdentifier, FCStringAnsi::Strlen(OpusIdentifier) + 1);
CompressedData.Serialize(&SampleRate, sizeof(uint16));
CompressedData.Serialize(&TrueSampleCount, sizeof(uint32));
CompressedData.Serialize(&NumChannels, sizeof(uint8));
CompressedData.Serialize(&NumFrames, sizeof(uint16));
}
void FOpusCompressor::SerialiseFrameData(FMemoryWriter& CompressedData, uint8* FrameData, uint16 FrameSize)
{
CompressedData.Serialize(&FrameSize, sizeof(uint16));
CompressedData.Serialize(FrameData, FrameSize);
}
int32 FOpusCompressor::AddDataChunk(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkSize)
{
TArray<uint8>& NewBuffer = *new(OutBuffers) TArray<uint8>;
NewBuffer.Empty(ChunkSize);
NewBuffer.AddUninitialized(ChunkSize);
FMemory::Memcpy(NewBuffer.GetData(), ChunkData, ChunkSize);
return ChunkSize;
}
@@ -0,0 +1,6 @@
// Georgy Treshchev 2021.
#include "PreimportedSoundAsset.h"
@@ -0,0 +1,17 @@
// Georgy Treshchev 2021.
#include "RuntimeAudioImporter.h"
#define LOCTEXT_NAMESPACE "FRuntimeAudioImporterModule"
void FRuntimeAudioImporterModule::StartupModule()
{
}
void FRuntimeAudioImporterModule::ShutdownModule()
{
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FRuntimeAudioImporterModule, RuntimeAudioImporter)
@@ -0,0 +1,646 @@
// Georgy Treshchev 2021.
#include "RuntimeAudioImporterLibrary.h"
#include "AudioDevice.h"
#include "AudioCompressionSettingsUtils.h"
#include "Interfaces/IAudioFormat.h"
// Compressors for platform-specific audio formats
#include "VorbisCompressor.h"
#include "OpusCompressor.h"
#include "AdvancedPCMCompressor.h"
URuntimeAudioImporterLibrary* URuntimeAudioImporterLibrary::CreateRuntimeAudioImporter()
{
URuntimeAudioImporterLibrary* Importer = NewObject<URuntimeAudioImporterLibrary>();
Importer->AddToRoot();
return Importer;
}
void URuntimeAudioImporterLibrary::ImportAudioFromFile(
const FString& FilePath, TEnumAsByte<EAudioFormat> Format, const FBuffersDetailsStruct& BuffersDetails)
{
if (!FPaths::FileExists(FilePath))
{
// Trying to get a file inside packaged build
IPlatformFile& FileManager = FPlatformFileManager::Get().GetPlatformFile();
OnResult_Internal(nullptr, ETranscodingStatus::AudioDoesNotExist);
return;
}
if (Format == EAudioFormat::Auto)
{
Format = GetAudioFormat(FilePath);
}
TArray<uint8> AudioBuffer;
if (!FFileHelper::LoadFileToArray(AudioBuffer, *FilePath))
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::LoadFileToArrayError);
return;
}
ImportAudioFromBuffer(AudioBuffer, Format, BuffersDetails);
}
void URuntimeAudioImporterLibrary::ImportAudioFromPreimportedSound(UPreimportedSoundAsset* PreimportedSoundAssetRef,
const FBuffersDetailsStruct& BuffersDetails)
{
ImportAudioFromBuffer(PreimportedSoundAssetRef->AudioDataArray, EAudioFormat::Mp3, BuffersDetails);
}
void URuntimeAudioImporterLibrary::ImportAudioFromBuffer(const TArray<uint8>& AudioDataArray,
const TEnumAsByte<EAudioFormat>& Format,
const FBuffersDetailsStruct& BuffersDetails)
{
AsyncTask(ENamedThreads::AnyThread, [=]()
{
ImportAudioFromBuffer_Internal(AudioDataArray, Format, BuffersDetails);
});
}
bool URuntimeAudioImporterLibrary::DestroySoundWave(USoundWave* ReadySoundWave)
{
if (ReadySoundWave->IsValidLowLevel())
{
//Begin USoundWave destroy (block changing some data)
ReadySoundWave->ConditionalBeginDestroy();
ReadySoundWave->BeginDestroy();
//Delete all audio data from physical memory
ReadySoundWave->RawData.RemoveBulkData();
ReadySoundWave->RemoveAudioResource();
AsyncTask(ENamedThreads::AudioThread, [=]()
{
ReadySoundWave->InvalidateCompressedData(true);
});
//Finish USoundWave destroy (complete object deletion as UObject)
ReadySoundWave->IsReadyForFinishDestroy(); //This is not needed for verification, but for the initial removal
ReadySoundWave->ConditionalFinishDestroy();
ReadySoundWave->FinishDestroy();
return true;
}
else
{
return false;
}
}
void URuntimeAudioImporterLibrary::ImportAudioFromBuffer_Internal(
const TArray<uint8>& AudioDataArray, const TEnumAsByte<EAudioFormat>& Format,
const FBuffersDetailsStruct& BuffersDetails)
{
BuffersDetailsInfo = BuffersDetails;
if (Format == EAudioFormat::Auto || Format == EAudioFormat::Invalid)
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::InvalidAudioFormat);
return;
}
// Transcoding importer Audio Data to PCM Data
if (!TranscodeAudioDataArrayToPCMData(AudioDataArray.GetData(), AudioDataArray.Num() - 2, Format))
{
return;
}
// A ready USoundWave static object
USoundWave* ReadySoundWave = DefineSoundWave();
if (ReadySoundWave == nullptr)
{
return;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(100);
// Callback Dispatcher OnResult, with the created SoundWave object
OnResult_Internal(ReadySoundWave, ETranscodingStatus::SuccessImporting);
}
USoundWave* URuntimeAudioImporterLibrary::DefineSoundWave()
{
USoundWave* SoundWaveRef = NewObject<USoundWave>(USoundWave::StaticClass());
if (!SoundWaveRef)
{
OnResult_Internal(nullptr, ETranscodingStatus::SoundWaveDeclarationError);
return nullptr;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(40);
// Fill SoundWave basic information (e.g. duration, number of channels, etc)
FillSoundWaveBasicInfo(SoundWaveRef);
// Callback Dispatcher OnProgress
OnProgress_Internal(45);
// Whether to fill the compressed buffer or not. If not filled, it is necessary to fill Raw (Wave) data so that the engine will automatically generate compressed data.
if (BuffersDetailsInfo.CompressionFormat == ECompressionFormat::RawData)
{
// Callback Dispatcher OnProgress
OnProgress_Internal(60);
// Transcode PCM to Raw (Wave) Data
if (!TranscodePCMToWAVData())
{
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
return nullptr;
}
// free unneeded PCM Data
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
// Callback Dispatcher OnProgress
OnProgress_Internal(75);
// Fill Raw (Wave) Data
FillRawWaveData(SoundWaveRef);
// Callback Dispatcher OnProgress
OnProgress_Internal(95);
return SoundWaveRef;
}
/* We don't need to fill RawPCMData buffer since it is not used at runtime during audio playback. But in case you want to fill this buffer - uncomment the code below (you should also remove some RawPCMData cleanups in code before it is executed)
sw->RawPCMData = static_cast<uint8*>(FMemory::Malloc(sw->RawPCMDataSize));
FMemory::Memmove(sw->RawPCMData, TranscodingFillInfo.PCMInfo.RawPCMData, sw->RawPCMDataSize);
*/
// Set a new sound compression quality
SoundWaveRef->CompressionQuality = BuffersDetailsInfo.SoundCompressionQuality;
// This variable won't be used anywhere, but it is recommended to set a decompression type to realtime since it will be decompressed at runtime
SoundWaveRef->DecompressionType = EDecompressionType::DTYPE_RealTime;
// Callback Dispatcher OnProgress
OnProgress_Internal(60);
// Transcode Raw PCM data to Compressed data
if (!TranscodePCMToCompressedData(SoundWaveRef))
{
return nullptr;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(75);
SoundWaveRef->SetPrecacheState(ESoundWavePrecacheState::NotStarted);
// Fill the compressed data
FillCompressedData(SoundWaveRef);
// Callback Dispatcher OnProgress
OnProgress_Internal(95);
return SoundWaveRef;
}
void URuntimeAudioImporterLibrary::FillSoundWaveBasicInfo(USoundWave* SoundWaveRef) const
{
SoundWaveRef->RawPCMDataSize = TranscodingFillInfo.PCMInfo.RawPCMDataSize;
SoundWaveRef->Duration = TranscodingFillInfo.SoundWaveBasicInfo.Duration;
SoundWaveRef->SetSampleRate(TranscodingFillInfo.SoundWaveBasicInfo.SampleRate);
SoundWaveRef->NumChannels = TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum;
SoundWaveRef->SoundGroup = ESoundGroup::SOUNDGROUP_Default;
if (SoundWaveRef->NumChannels == 4)
{
SoundWaveRef->bIsAmbisonics = 1;
}
}
void URuntimeAudioImporterLibrary::FillRawWaveData(USoundWave* SoundWaveRef) const
{
SoundWaveRef->RawData.Lock(LOCK_READ_WRITE);
FMemory::Memmove(SoundWaveRef->RawData.Realloc(TranscodingFillInfo.WAVInfo.WaveDataSize),
TranscodingFillInfo.WAVInfo.WaveData, TranscodingFillInfo.WAVInfo.WaveDataSize);
SoundWaveRef->RawData.Unlock();
}
void URuntimeAudioImporterLibrary::FillCompressedData(USoundWave* SoundWaveRef)
{
// Get the name of the runtime format that will be used during audio playback
const FName CurrentAudioFormat = GEngine->GetAudioDeviceManager()->GetActiveAudioDevice()->
GetRuntimeFormat(SoundWaveRef);
SoundWaveRef->SetPrecacheState(ESoundWavePrecacheState::InProgress);
// Fill the compressed data
FByteBulkData* CompressedBulkData = &SoundWaveRef->CompressedFormatData.GetFormat(
GetPlatformSpecificFormat(CurrentAudioFormat));
CompressedBulkData->Lock(LOCK_READ_WRITE);
FMemory::Memmove(CompressedBulkData->Realloc(TranscodingFillInfo.CompressedInfo.CompressedFormatData.Num()),
TranscodingFillInfo.CompressedInfo.CompressedFormatData.GetData(),
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Num());
CompressedBulkData->Unlock();
SoundWaveRef->SetPrecacheState(ESoundWavePrecacheState::Done);
}
bool URuntimeAudioImporterLibrary::TranscodePCMToCompressedData(USoundWave* SoundWaveToGetData)
{
// Raw PCM data Array
TArray<uint8> RawPCMArray = TArray<uint8>(TranscodingFillInfo.PCMInfo.RawPCMData,
TranscodingFillInfo.PCMInfo.RawPCMDataSize);
// Sound quality information for compression purposes
FSoundQualityInfo SoundQualityInfo = GenerateSoundQualityInfo(SoundWaveToGetData);
switch (BuffersDetailsInfo.CompressionFormat)
{
case OggVorbis:
{
if (!FVorbisCompressor::GenerateCompressedData(RawPCMArray, SoundQualityInfo,
TranscodingFillInfo.CompressedInfo.CompressedFormatData))
{
// Clearing all data in case of error for performance reasons
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::GenerateCompressedDataError);
return false;
}
break;
}
case OggOpus:
{
if (!FOpusCompressor::GenerateCompressedData(RawPCMArray, SoundQualityInfo,
TranscodingFillInfo.CompressedInfo.CompressedFormatData))
{
// Clearing all data in case of error for performance reasons
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::GenerateCompressedDataError);
return false;
}
break;
}
case ADPCMLPCM:
{
if (!FAdvancedPCMCompressor::GenerateCompressedData(RawPCMArray, SoundQualityInfo,
TranscodingFillInfo.CompressedInfo.CompressedFormatData)
)
{
// Clearing all data in case of error for performance reasons
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::GenerateCompressedDataError);
return false;
}
break;
}
default:
{
// Clearing all data in case of error for performance reasons
TranscodingFillInfo.CompressedInfo.CompressedFormatData.Empty();
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::InvalidCompressedFormat);
return false;
}
}
// When the compressed data is generated, RawPCMData is no longer needed.
RawPCMArray.Empty();
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
// Callback Dispatcher OnProgress
OnProgress_Internal(85);
return true;
}
/**
* Including dr_wav, dr_mp3 and dr_flac libraries
*/
#include "ThirdParty/dr_wav.h"
#include "ThirdParty/dr_mp3.h"
#include "ThirdParty/dr_flac.h"
/**
* Replacing standard CPP memory functions (malloc, realloc, free) with memory management functions that are maintained by Epic as the engine evolves.
*/
void* Unreal_Malloc(size_t sz, void* pUserData)
{
return FMemory::Malloc(sz);
}
void* Unreal_Realloc(void* p, size_t sz, void* pUserData)
{
return FMemory::Realloc(p, sz);
}
void Unreal_Free(void* p, void* pUserData)
{
FMemory::Free(p);
}
bool URuntimeAudioImporterLibrary::TranscodeAudioDataArrayToPCMData(const uint8* AudioData, uint32 AudioDataSize,
TEnumAsByte<EAudioFormat> Format)
{
OnProgress_Internal(5);
switch (Format)
{
case EAudioFormat::Mp3:
{
drmp3_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = nullptr;
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
allocationCallbacksDecoding.onFree = Unreal_Free;
drmp3 mp3;
if (!drmp3_init_memory(&mp3, AudioData, AudioDataSize, &allocationCallbacksDecoding))
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::FailedToReadAudioDataArray);
return false;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(10);
TranscodingFillInfo.PCMInfo.RawPCMData = static_cast<uint8*>(FMemory::Malloc(
static_cast<size_t>(drmp3_get_pcm_frame_count(&mp3)) * mp3.channels * sizeof(int16_t)));
// Callback Dispatcher OnProgress
OnProgress_Internal(15);
TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames = drmp3_read_pcm_frames_s16(
&mp3, drmp3_get_pcm_frame_count(&mp3),
reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
// Callback Dispatcher OnProgress
OnProgress_Internal(35);
TranscodingFillInfo.PCMInfo.RawPCMDataSize = static_cast<uint32>(TranscodingFillInfo.PCMInfo.
RawPCMNumOfFrames * mp3.channels * sizeof(int16_t));
TranscodingFillInfo.SoundWaveBasicInfo.Duration = static_cast<float>(drmp3_get_pcm_frame_count(&mp3)) / mp3.
sampleRate;
TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum = mp3.channels;
TranscodingFillInfo.SoundWaveBasicInfo.SampleRate = mp3.sampleRate;
drmp3_uninit(&mp3);
return true;
}
case EAudioFormat::Wav:
{
drwav_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = nullptr;
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
allocationCallbacksDecoding.onFree = Unreal_Free;
drwav wav;
if (!drwav_init_memory(&wav, AudioData, AudioDataSize, &allocationCallbacksDecoding))
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::FailedToReadAudioDataArray);
return false;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(10);
TranscodingFillInfo.PCMInfo.RawPCMData = static_cast<uint8*>(FMemory::Malloc(
static_cast<size_t>(wav.totalPCMFrameCount) * wav.channels * sizeof(int16_t)));
// Callback Dispatcher OnProgress
OnProgress_Internal(15);
TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames = drwav_read_pcm_frames_s16(
&wav, wav.totalPCMFrameCount, reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
// Callback Dispatcher OnProgress
OnProgress_Internal(35);
TranscodingFillInfo.PCMInfo.RawPCMDataSize = static_cast<uint32>(TranscodingFillInfo.PCMInfo.
RawPCMNumOfFrames * wav.channels * sizeof(int16_t));
TranscodingFillInfo.SoundWaveBasicInfo.Duration = static_cast<float>(wav.totalPCMFrameCount) / wav.
sampleRate;
TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum = wav.channels;
TranscodingFillInfo.SoundWaveBasicInfo.SampleRate = wav.sampleRate;
drwav_uninit(&wav);
return true;
}
case EAudioFormat::Flac:
{
drflac_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = nullptr;
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
allocationCallbacksDecoding.onFree = Unreal_Free;
drflac* pFlac = drflac_open_memory(AudioData, AudioDataSize, &allocationCallbacksDecoding);
if (pFlac == nullptr)
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::FailedToReadAudioDataArray);
return false;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(10);
TranscodingFillInfo.PCMInfo.RawPCMData = static_cast<uint8*>(FMemory::Malloc(
static_cast<size_t>(pFlac->totalPCMFrameCount) * pFlac->channels * sizeof(int16_t)));
// Callback Dispatcher OnProgress
OnProgress_Internal(15);
TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames = drflac_read_pcm_frames_s16(
pFlac, pFlac->totalPCMFrameCount, reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
// Callback Dispatcher OnProgress
OnProgress_Internal(35);
TranscodingFillInfo.PCMInfo.RawPCMDataSize = static_cast<uint32>(TranscodingFillInfo.PCMInfo.
RawPCMNumOfFrames * pFlac->channels * sizeof(int16_t));
TranscodingFillInfo.SoundWaveBasicInfo.Duration = static_cast<float>(pFlac->totalPCMFrameCount) / pFlac->
sampleRate;
TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum = pFlac->channels;
TranscodingFillInfo.SoundWaveBasicInfo.SampleRate = pFlac->sampleRate;
drflac_close(pFlac);
return true;
}
default:
{
// Callback Dispatcher OnResult
OnResult_Internal(nullptr, ETranscodingStatus::InvalidAudioFormat);
return false;
}
}
}
bool URuntimeAudioImporterLibrary::TranscodePCMToWAVData()
{
drwav wavEncode;
drwav_data_format format;
format.container = drwav_container_riff;
format.format = DR_WAVE_FORMAT_PCM;
format.channels = TranscodingFillInfo.SoundWaveBasicInfo.ChannelsNum;
format.sampleRate = TranscodingFillInfo.SoundWaveBasicInfo.SampleRate;
format.bitsPerSample = 16; // Unreal Engine supports only 16-bit rate
drwav_allocation_callbacks allocationCallbacks;
allocationCallbacks.pUserData = nullptr;
allocationCallbacks.onMalloc = Unreal_Malloc;
allocationCallbacks.onRealloc = Unreal_Realloc;
allocationCallbacks.onFree = Unreal_Free;
// Initializing PCM data writing to memory
if (!drwav_init_memory_write(&wavEncode, reinterpret_cast<void**>(&TranscodingFillInfo.WAVInfo.WaveData),
reinterpret_cast<size_t*>(&TranscodingFillInfo.WAVInfo.WaveDataSize), &format,
&allocationCallbacks))
{
FMemory::Free(TranscodingFillInfo.PCMInfo.RawPCMData);
OnResult_Internal(nullptr, ETranscodingStatus::PCMToWavDataError);
return false;
}
// Callback Dispatcher OnProgress
OnProgress_Internal(40);
// Write Raw PCM Data
drwav_write_pcm_frames(&wavEncode, TranscodingFillInfo.PCMInfo.RawPCMNumOfFrames,
reinterpret_cast<int16_t*>(TranscodingFillInfo.PCMInfo.RawPCMData));
// Callback Dispatcher OnProgress
OnProgress_Internal(60);
drwav_uninit(&wavEncode);
return true;
}
TEnumAsByte<EAudioFormat> URuntimeAudioImporterLibrary::GetAudioFormat(const FString& FilePath)
{
if (FPaths::GetExtension(FilePath, false).Equals(TEXT("mp3"), ESearchCase::IgnoreCase))
{
return EAudioFormat::Mp3;
}
if (FPaths::GetExtension(FilePath, false).Equals(TEXT("wav"), ESearchCase::IgnoreCase))
{
return EAudioFormat::Wav;
}
if (FPaths::GetExtension(FilePath, false).Equals(TEXT("flac"), ESearchCase::IgnoreCase))
{
return EAudioFormat::Flac;
}
return EAudioFormat::Invalid;
}
FSoundQualityInfo URuntimeAudioImporterLibrary::GenerateSoundQualityInfo(USoundWave* SoundWaveToGetData) const
{
FSoundQualityInfo SoundQualityInfo;
SoundQualityInfo.Duration = SoundWaveToGetData->Duration;
SoundQualityInfo.Quality = SoundWaveToGetData->CompressionQuality;
SoundQualityInfo.NumChannels = SoundWaveToGetData->NumChannels;
// since we cannot get the Sample Rate from the SoundWave, we will get it from the data of the initial PCM transcoding
SoundQualityInfo.SampleRate = TranscodingFillInfo.SoundWaveBasicInfo.SampleRate;
SoundQualityInfo.SampleDataSize = SoundWaveToGetData->RawPCMDataSize;
SoundQualityInfo.bStreaming = SoundWaveToGetData->IsStreaming(*FPlatformCompressionUtilities::GetCookOverrides());
SoundQualityInfo.DebugName = SoundWaveToGetData->GetFullName();
return SoundQualityInfo;
}
FName URuntimeAudioImporterLibrary::GetPlatformSpecificFormat(const FName Format)
{
const FPlatformAudioCookOverrides* CompressionOverrides = FPlatformCompressionUtilities::GetCookOverrides();
// Platforms that require compression overrides get concatenated formats.
#if WITH_EDITOR
FName PlatformSpecificFormat;
if (CompressionOverrides)
{
FString HashedString = *Format.ToString();
FPlatformAudioCookOverrides::GetHashSuffix(CompressionOverrides, HashedString);
PlatformSpecificFormat = *HashedString;
}
else
{
PlatformSpecificFormat = Format;
}
#else
// Cache the concatenated hash:
static FName PlatformSpecificFormat;
static FName CachedFormat;
if (!Format.IsEqual(CachedFormat))
{
if (CompressionOverrides)
{
FString HashedString = *Format.ToString();
FPlatformAudioCookOverrides::GetHashSuffix(CompressionOverrides, HashedString);
PlatformSpecificFormat = *HashedString;
}
else
{
PlatformSpecificFormat = Format;
}
CachedFormat = Format;
}
#endif // WITH_EDITOR
return PlatformSpecificFormat;
}
void URuntimeAudioImporterLibrary::OnProgress_Internal(int32 Percentage)
{
AsyncTask(ENamedThreads::GameThread, [=]()
{
OnProgress.Broadcast(Percentage);
});
}
void URuntimeAudioImporterLibrary::OnResult_Internal(USoundWave* ReadySoundWave,
const TEnumAsByte<ETranscodingStatus>& Status)
{
AsyncTask(ENamedThreads::GameThread, [=]()
{
RemoveFromRoot();
OnResult.Broadcast(this, ReadySoundWave, Status);
});
}
@@ -0,0 +1,202 @@
// Georgy Treshchev 2021.
#include "VorbisCompressor.h"
#include "Interfaces/IAudioFormat.h"
#include "Serialization/MemoryWriter.h"
#include "VorbisAudioInfo.h"
#if PLATFORM_SUPPORTS_VORBIS_CODEC
THIRD_PARTY_INCLUDES_START
#include "vorbis/vorbisenc.h"
#include "vorbis/vorbisfile.h"
THIRD_PARTY_INCLUDES_END
#endif
#define VORBIS_QUALITY_MODIFIER 0.85
#define SAMPLES_TO_READ 1024
#define SAMPLE_SIZE ( ( uint32 )sizeof( short ) )
bool FVorbisCompressor::GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& CompressedDataStore)
{
#if WITH_OGGVORBIS
{
short ReadBuffer[SAMPLES_TO_READ * SAMPLE_SIZE * 2];
ogg_stream_state Stream_State;
ogg_page Ogg_Page;
ogg_packet Ogg_Packet;
vorbis_info Vorbis_Info;
vorbis_comment Vorbis_Comment;
vorbis_dsp_state Dsp_State;
vorbis_block Vorbis_Block;
uint32 i;
bool bEndOfBitstream;
CompressedDataStore.Empty();
FMemoryWriter CompressedData(CompressedDataStore);
uint32 BufferOffset = 0;
float CompressionQuality = (float)(QualityInfo.Quality * VORBIS_QUALITY_MODIFIER) / 100.0f;
CompressionQuality = FMath::Clamp(CompressionQuality, -0.1f, 1.0f);
vorbis_info_init(&Vorbis_Info);
if (vorbis_encode_init_vbr(&Vorbis_Info, QualityInfo.NumChannels, QualityInfo.SampleRate, CompressionQuality))
{
return false;
}
vorbis_comment_init( &Vorbis_Comment );
vorbis_comment_add_tag( &Vorbis_Comment, "ENCODER", "RuntimeAudioImporter" );
vorbis_analysis_init(&Dsp_State, &Vorbis_Info);
vorbis_block_init(&Dsp_State, &Vorbis_Block);
ogg_stream_init(&Stream_State, 0);
ogg_packet header;
ogg_packet header_comm;
ogg_packet header_code;
vorbis_analysis_headerout(&Dsp_State, &Vorbis_Comment, &header, &header_comm, &header_code);
ogg_stream_packetin(&Stream_State, &header);
ogg_stream_packetin(&Stream_State, &header_comm);
ogg_stream_packetin(&Stream_State, &header_code);
while (true)
{
int result = ogg_stream_flush(&Stream_State, &Ogg_Page);
if (result == 0)
{
break;
}
CompressedData.Serialize(Ogg_Page.header, Ogg_Page.header_len);
CompressedData.Serialize(Ogg_Page.body, Ogg_Page.body_len);
}
bEndOfBitstream = false;
while (!bEndOfBitstream)
{
uint32 BytesToRead = FMath::Min(SAMPLES_TO_READ * QualityInfo.NumChannels * SAMPLE_SIZE,
QualityInfo.SampleDataSize - BufferOffset);
FMemory::Memcpy(ReadBuffer, SrcBuffer.GetData() + BufferOffset, BytesToRead);
BufferOffset += BytesToRead;
if (BytesToRead == 0)
{
vorbis_analysis_wrote(&Dsp_State, 0);
}
else
{
float** buffer = vorbis_analysis_buffer(&Dsp_State, SAMPLES_TO_READ);
if (QualityInfo.NumChannels == 1)
{
for (i = 0; i < BytesToRead / SAMPLE_SIZE; i++)
{
buffer[0][i] = (ReadBuffer[i]) / 32768.0f;
}
}
else
{
for (i = 0; i < BytesToRead / (SAMPLE_SIZE * 2); i++)
{
buffer[0][i] = (ReadBuffer[i * 2]) / 32768.0f;
buffer[1][i] = (ReadBuffer[i * 2 + 1]) / 32768.0f;
}
}
vorbis_analysis_wrote(&Dsp_State, i);
}
while (vorbis_analysis_blockout(&Dsp_State, &Vorbis_Block) == 1)
{
vorbis_analysis(&Vorbis_Block, NULL);
vorbis_bitrate_addblock(&Vorbis_Block);
while (vorbis_bitrate_flushpacket(&Dsp_State, &Ogg_Packet))
{
ogg_stream_packetin(&Stream_State, &Ogg_Packet);
while (!bEndOfBitstream)
{
int result = ogg_stream_pageout(&Stream_State, &Ogg_Page);
if (result == 0)
{
break;
}
CompressedData.Serialize(Ogg_Page.header, Ogg_Page.header_len);
CompressedData.Serialize(Ogg_Page.body, Ogg_Page.body_len);
if (ogg_page_eos(&Ogg_Page))
{
bEndOfBitstream = true;
}
}
}
}
}
ogg_stream_clear(&Stream_State);
vorbis_block_clear(&Vorbis_Block);
vorbis_dsp_clear(&Dsp_State);
vorbis_comment_clear(&Vorbis_Comment);
vorbis_info_clear(&Vorbis_Info);
}
return CompressedDataStore.Num() > 0;
#else
return false;
#endif // WITH_OGGVOBVIS
}
TArray<int32> FVorbisCompressor::GetChannelOrder(int32 NumChannels)
{
TArray<int32> ChannelOrder;
switch (NumChannels)
{
case 6:
{
for (int32 i = 0; i < NumChannels; i++)
{
ChannelOrder.Add(VorbisChannelInfo::Order[NumChannels - 1][i]);
}
break;
}
default:
{
for (int32 i = 0; i < NumChannels; i++)
{
ChannelOrder.Add(i);
}
break;
}
}
return ChannelOrder;
}
int32 FVorbisCompressor::GetMinimumSizeForInitialChunk(FName Format, const TArray<uint8>& SrcBuffer)
{
return 6 * 1024;
}
void FVorbisCompressor::AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize)
{
TArray<uint8>& NewBuffer = *new(OutBuffers) TArray<uint8>;
NewBuffer.Empty(ChunkReserveSize);
}
void FVorbisCompressor::AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkDataSize)
{
TArray<uint8>& TargetBuffer = OutBuffers[OutBuffers.Num() - 1];
TargetBuffer.Append(ChunkData, ChunkDataSize);
}
@@ -0,0 +1,25 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
struct FSoundQualityInfo;
struct RiffDataChunk
{
uint32 ID;
uint32 DataSize;
uint8* Data;
};
class RUNTIMEAUDIOIMPORTER_API FAdvancedPCMCompressor
{
public:
static bool GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& OutBuffer);
static void AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize);
static void AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkDataSize);
};
@@ -0,0 +1,28 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
struct FSoundQualityInfo;
class RUNTIMEAUDIOIMPORTER_API FOpusCompressor
{
public:
static bool GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& OutBuffer);
static uint16 GetBestOutputSampleRate(int32 SampleRate);
static bool ResamplePCM(uint32 NumChannels, const TArray<uint8>& InBuffer, uint32 InSampleRate,
TArray<uint8>& OutBuffer, uint32 OutSampleRate);
static int32 GetBitRateFromQuality(FSoundQualityInfo& QualityInfo);
static void SerializeHeaderData(FMemoryWriter& CompressedData, uint16 SampleRate, uint32 TrueSampleCount,
uint8 NumChannels, uint16 NumFrames);
static void SerialiseFrameData(FMemoryWriter& CompressedData, uint8* FrameData, uint16 FrameSize);
static int32 AddDataChunk(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkSize);
};
@@ -0,0 +1,35 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
#include "PreimportedSoundAsset.generated.h"
/**
* Pre-imported asset which collects MP3 audio data. Used if you want to load the MP3 file into the editor in advance
*/
UCLASS(BlueprintType, Category = "RuntimeAudioImporter")
class RUNTIMEAUDIOIMPORTER_API UPreimportedSoundAsset : public UObject
{
GENERATED_BODY()
public:
/** Audio data array. Only MP3 data is intended to be here */
UPROPERTY()
TArray<uint8> AudioDataArray;
/** Information about the basic details of an audio file. Used only for convenience in the editor */
#if WITH_EDITORONLY_DATA
UPROPERTY(Category = "File Path", VisibleAnywhere, Meta = (DisplayName = "Source file path"))
FString SourceFilePath;
UPROPERTY(Category = "Info", VisibleAnywhere, Meta = (DisplayName = "Sound duration"))
FString SoundDuration;
UPROPERTY(Category = "Info", VisibleAnywhere, Meta = (DisplayName = "Number of channels"))
int32 NumberOfChannels;
UPROPERTY(Category = "Info", VisibleAnywhere, Meta = (DisplayName = "Sample rate"))
int32 SampleRate;
#endif
};
@@ -0,0 +1,16 @@
// Respirant 2020.
#pragma once
#include "Modules/ModuleManager.h"
class FRuntimeAudioImporterModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
@@ -0,0 +1,358 @@
// Georgy Treshchev 2021.
#pragma once
#include "Sound/SoundWave.h"
#include "Async/Async.h"
#include "PreimportedSoundAsset.h"
#include "RuntimeAudioImporterLibrary.generated.h"
/** Possible audio importing results */
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
enum ETranscodingStatus
{
/** Success importing */
SuccessImporting UMETA(DisplayName = "Success Importing"),
/** Failed to read Audio Data Array */
FailedToReadAudioDataArray UMETA(DisplayName = "Failed to read Audio Data Array"),
/** SoundWave declaration error */
SoundWaveDeclarationError UMETA(DisplayName = "SoundWave declaration error"),
/** Invalid audio format (Can't determine the format of the audio file) */
InvalidAudioFormat UMETA(DisplayName = "Invalid audio format"),
/** The audio file does not exist */
AudioDoesNotExist UMETA(DisplayName = "Audio does not exist"),
/** Load file to array error */
LoadFileToArrayError UMETA(DisplayName = "Load file to array error"),
/** Transcoding PCM to Wav data error */
PCMToWavDataError UMETA(DisplayName = "PCM to Wav data error"),
/** Generate compressed data error */
GenerateCompressedDataError UMETA(DisplayName = "Generate compressed data error"),
/** Invalid compressed format error */
InvalidCompressedFormat UMETA(DisplayName = "Invalid compressed format")
};
/** Possible audio formats (extensions) */
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
enum EAudioFormat
{
/** Determine format automatically */
Auto UMETA(DisplayName = "Determine format automatically"),
/** MP3 format */
Mp3 UMETA(DisplayName = "mp3"),
/** WAV format */
Wav UMETA(DisplayName = "wav"),
/** FLAC format */
Flac UMETA(DisplayName = "flac"),
/** Invalid format */
Invalid UMETA(DisplayName = "invalid (not defined format, CPP use only)", Hidden)
};
/** List of all supported compression formats */
UENUM(BlueprintType, Category = "RuntimeAudioImporter")
enum ECompressionFormat
{
/** Raw (Wav) data format. Not recommended because it doesn't work on packaged build */
RawData UMETA(DisplayName = "Raw Data"),
/** Ogg Vorbis format. It is used, for example, on Android and Windows */
OggVorbis UMETA(DisplayName = "Ogg Vorbis"),
/** ADPCM or LPCM formats. LPCM will be used if the compression quality is 100%, otherwise ADPCM */
ADPCMLPCM UMETA(DisplayName = "ADPCM/LPCM"),
/** Ogg Opus format. Mostly not used anywhere, but can be used for personal purposes */
OggOpus UMETA(DisplayName = "Ogg Opus")
};
/** Information about which buffers to fill */
USTRUCT(BlueprintType)
struct FBuffersDetailsStruct
{
GENERATED_BODY()
/** Needed compression format */
UPROPERTY(BlueprintReadWrite)
TEnumAsByte<ECompressionFormat> CompressionFormat = OggVorbis;
/**
* Sound compression quality. The range of numbers is from 0 to 100, in percent. How much to compress the sound, where 0 is the maximum loss, 100 is the minimum.
* Not used if CompressionFormat is "RawData"
*/
UPROPERTY(BlueprintReadWrite)
int32 SoundCompressionQuality = 100;
};
/** Basic SoundWave data. CPP use only. */
struct FSoundWaveBasicStruct
{
/** Number of channels */
int32 ChannelsNum;
/** Sample rate (samples per second, sampling frequency) */
uint32 SampleRate;
/** Sound wave duration, sec */
float Duration;
};
/** Raw PCM Data */
struct FPCMStruct
{
/** Raw PCM data pointer */
uint8* RawPCMData;
/** Number of PCM frames */
uint64 RawPCMNumOfFrames;
/** Raw PCM data size */
uint32 RawPCMDataSize;
};
/** Wav (or Raw) Data */
struct FWAVStruct
{
/** Wave data pointer */
uint8* WaveData;
/** Wave data size */
int32 WaveDataSize;
};
/** Compressed Data (Ogg Vorbis, ADPCM, etc) */
struct FCompressedStruct
{
/** Array, which contains compressed format data */
TArray<uint8> CompressedFormatData;
};
/** Main, mostly in-memory information (like PCM, Wav, etc) */
struct FTranscodingFillStruct
{
/** SoundWave basic info (e.g. duration, number of channels, etc) */
FSoundWaveBasicStruct SoundWaveBasicInfo;
/** Raw PCM Data info */
FPCMStruct PCMInfo;
/** Wav (or Raw) Data info */
FWAVStruct WAVInfo;
/** Compressed Data info (Ogg Vorbis, ADPCM, etc) */
FCompressedStruct CompressedInfo;
};
// Forward declaration of the FSoundQualityInfo structure
struct FSoundQualityInfo;
/**
* Declare delegate which will be called during the transcoding process
*
* @param Percentage Percentage of importing completed (0-100%)
*/
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnProgress, const int32, Percentage);
/**
* Declare a delegate that will be called on the transcoding result
*
* @param RuntimeAudioImporterObject Runtime Audio Importer object reference
* @param ReadySoundWave Ready SoundWave object reference
* @param Status TranscodingStatus Enum in case an error occurs
*/
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnResult, class URuntimeAudioImporterLibrary*,
RuntimeAudioImporterObjectRef,
const USoundWave*, SoundWaveRef,
const TEnumAsByte < ETranscodingStatus >&, Status);
/**
* Runtime Audio Importer object
* Allows you to do various things with audio files in real time, for example, import audio files into a SoundWave engine object
*/
UCLASS(BlueprintType, Category = "RuntimeAudioImporter")
class RUNTIMEAUDIOIMPORTER_API URuntimeAudioImporterLibrary : public UObject
{
GENERATED_BODY()
public:
/** Bind to know when the transcoding is on progress */
UPROPERTY(BlueprintAssignable, Category = "RuntimeAudioImporter")
FOnProgress OnProgress;
/** Bind to know when the transcoding is complete (even if it fails) */
UPROPERTY(BlueprintAssignable, Category = "RuntimeAudioImporter")
FOnResult OnResult;
/** Transcoding fill info. CPP use only */
FTranscodingFillStruct TranscodingFillInfo = FTranscodingFillStruct();
/** Buffers details info. CPP use only */
FBuffersDetailsStruct BuffersDetailsInfo = FBuffersDetailsStruct();
/**
* Instantiates a RuntimeAudioImporter object
*
* @return The RuntimeAudioImporter object. Bind to it's OnProgress and OnResult events to know when it is in the process of transcoding and imported
* @note You must place the returned reference to the RuntimeAudioImporterLibrary in a separate variable so that the UObject is not removed during garbage collection
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Create, Audio, Runtime, MP3, FLAC, WAV"), Category =
"RuntimeAudioImporter")
static URuntimeAudioImporterLibrary* CreateRuntimeAudioImporter();
/**
* Transcode audio file to SoundWave object
*
* @param FilePath Path to the audio file to import
* @param Format Audio file format (extension)
* @param BuffersDetails Information about which buffers to fill
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV"),
Category = "RuntimeAudioImporter")
void ImportAudioFromFile(const FString& FilePath, TEnumAsByte<EAudioFormat> Format,
const FBuffersDetailsStruct& BuffersDetails);
/**
* Import audio file from the preimported sound asset
*
* @param PreimportedSoundAssetRef PreimportedSoundAsset object reference. Should contains "BaseAudioDataArray" buffer
* @param BuffersDetails Information about which buffers to fill
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3"),
Category = "RuntimeAudioImporter")
void ImportAudioFromPreimportedSound(UPreimportedSoundAsset* PreimportedSoundAssetRef,
const FBuffersDetailsStruct& BuffersDetails);
/**
* Transcode audio file to USoundWave static object
*
* @param AudioDataArray Array of Audio byte data
* @param Format Audio file format (extension)
* @param BuffersDetails Information about which buffers to fill
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "Importer, Transcoder, Converter, Runtime, MP3, FLAC, WAV"),
Category = "RuntimeAudioImporter")
void ImportAudioFromBuffer(const TArray<uint8>& AudioDataArray, const TEnumAsByte<EAudioFormat>& Format,
const FBuffersDetailsStruct& BuffersDetails);
/**
* Destroy SoundWave object. After the SoundWave is no longer needed, you need to call this function to free memory
*
* @param ReadySoundWave SoundWave object reference need to be destroyed
* @return Whether the destruction of the SoundWave was successful or not
*/
UFUNCTION(BlueprintCallable, meta = (Keywords = "MP3, FLAC, WAV, Destroy"), Category = "RuntimeAudioImporter")
static bool DestroySoundWave(USoundWave* ReadySoundWave);
private:
/**
* Internal main audio transcoding function
*
* @param AudioDataArray Array of Audio byte data
* @param Format Audio file format (extension)
* @param BuffersDetails Information about which buffers to fill
*/
void ImportAudioFromBuffer_Internal(const TArray<uint8>& AudioDataArray,
const TEnumAsByte<EAudioFormat>& Format,
const FBuffersDetailsStruct& BuffersDetails);
/**
* Define SoundWave object reference
*
* @return Returns a ready USoundWave object
*/
USoundWave* DefineSoundWave();
/**
* Fill SoundWave basic information (e.g. duration, number of channels, etc)
*
* @param SoundWaveRef SoundWave object reference
*/
void FillSoundWaveBasicInfo(USoundWave* SoundWaveRef) const;
/**
* Fill SoundWave Raw (Wave) data buffer
*
* @param SoundWaveRef SoundWave object reference
*/
void FillRawWaveData(USoundWave* SoundWaveRef) const;
/**
* Fill SoundWave compressed data buffer
*
* @param SoundWaveRef SoundWave object reference
*/
void FillCompressedData(USoundWave* SoundWaveRef);
/**
* Transcode Audio from PCM to 16-bit WAV data
*
* @return Whether the transcoding was successful or not
*/
bool TranscodePCMToCompressedData(USoundWave* SoundWaveToGetData);
/**
* Transcode Audio from Audio Data to PCM Data
*
* @param AudioData Pointer to memory location of Audio byte data
* @param AudioDataSize Memory size allocated for Audio byte data
* @param Format Format of the audio file (e.g. mp3. flac, etc)
* @return Whether the transcoding was successful or not
*/
bool TranscodeAudioDataArrayToPCMData(const uint8* AudioData, uint32 AudioDataSize,
TEnumAsByte<EAudioFormat> Format);
/**
* Transcode Audio from PCM to 16-bit WAV data
*
* @return Whether the transcoding was successful or not
*/
bool TranscodePCMToWAVData();
/**
* Get Audio Format by extension
*
* @param FilePath File path, where to find the format (by extension)
* @return Returns the found audio format (e.g. mp3. flac, etc) by EAudioFormat Enum
*/
UFUNCTION(BlueprintCallable, Category = "RuntimeAudioImporter")
static TEnumAsByte<EAudioFormat> GetAudioFormat(const FString& FilePath);
/**
* Generate FSoundQualityInfo Enum based on SoundWave and already defined Sample Rate
*/
FSoundQualityInfo GenerateSoundQualityInfo(USoundWave* SoundWaveToGetData) const;
/**
* Get the platform-specific format name based on the base format to find the compressed buffer to fill
*
* @param Format Base format name (it can be "OGG", "LPCM", etc)
*/
static FName GetPlatformSpecificFormat(const FName Format);
/**
* Audio transcoding progress callback
* @param Percentage Percentage of importing completion (0-100%)
*/
void OnProgress_Internal(int32 Percentage);
/**
* Audio transcoding finished callback
*
* @param ReadySoundWave A ready SoundWave object
* @param Status TranscodingStatus Enum in case an error occurs
*/
void OnResult_Internal(USoundWave* ReadySoundWave, const TEnumAsByte<ETranscodingStatus>& Status);
};
@@ -0,0 +1,22 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
struct FSoundQualityInfo;
class RUNTIMEAUDIOIMPORTER_API FVorbisCompressor
{
public:
static bool GenerateCompressedData(const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo,
TArray<uint8>& OutBuffer);
static int32 GetMinimumSizeForInitialChunk(FName Format, const TArray<uint8>& SrcBuffer);
static TArray<int32> GetChannelOrder(int32 NumChannels);
static void AddNewChunk(TArray<TArray<uint8>>& OutBuffers, int32 ChunkReserveSize);
static void AddChunkData(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkDataSize);
};
@@ -0,0 +1,55 @@
// Georgy Treshchev 2021.
using System;
using System.IO;
using UnrealBuildTool;
public class RuntimeAudioImporter : ModuleRules
{
public RuntimeAudioImporter(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
//PublicDefinitions.Add("WITH_OGGVORBIS=0");
PublicDefinitions.AddRange(
new string[] {
"DR_WAV_IMPLEMENTATION=1",
"DR_MP3_IMPLEMENTATION=1",
"DR_FLAC_IMPLEMENTATION=1"
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"AudioPlatformConfiguration"
}
);
AddEngineThirdPartyPrivateStaticDependencies(Target,
"UEOgg",
"Vorbis",
"libOpus"
);
PublicDefinitions.Add("WITH_OGGVORBIS");
}
}
@@ -0,0 +1,121 @@
// Georgy Treshchev 2021.
#include "PreimportedSoundFactory.h"
#include "PreimportedSoundAsset.h" //maybe #include "RuntimeAudioImporter/Public/PreimportedSoundAsset.h"
#include "Misc/FileHelper.h"
/** Defining custom logging */
DEFINE_LOG_CATEGORY(LogPreimportedSoundFactory)
#include "ThirdParty/dr_mp3.h"
/**
* Replacing standard CPP memory functions (malloc, realloc, free) with memory management functions that are maintained by Epic as the engine evolves.
*/
void* Unreal_Malloc(size_t sz, void* pUserData)
{
return FMemory::Malloc(sz);
}
void* Unreal_Realloc(void* p, size_t sz, void* pUserData)
{
return FMemory::Realloc(p, sz);
}
void Unreal_Free(void* p, void* pUserData)
{
FMemory::Free(p);
}
UPreimportedSoundFactory::UPreimportedSoundFactory()
{
Formats.Add(TEXT("mp3;MP3 Preimported Audio Format"));
Formats.Add(TEXT("imp;IMP (the same as MP3) Preimported Audio Format"));
SupportedClass = UPreimportedSoundAsset::StaticClass();
bCreateNew = false; // turned off for import
bEditAfterNew = false; // turned off for import
bEditorImport = true;
bText = false;
}
bool UPreimportedSoundFactory::FactoryCanImport(const FString& Filename)
{
return FPaths::GetExtension(Filename).Equals(TEXT("mp3")) || FPaths::GetExtension(Filename).Equals(TEXT("imp"));
}
UObject* UPreimportedSoundFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName,
EObjectFlags Flags, const FString& Filename, const TCHAR* Parms,
FFeedbackContext* Warn, bool& bOutOperationCanceled)
{
UPreimportedSoundAsset* PreimportedSoundAsset = nullptr;
TArray<uint8> AudioDataArray;
if (FFileHelper::LoadFileToArray(AudioDataArray, *Filename))
{
drmp3_allocation_callbacks allocationCallbacksDecoding;
allocationCallbacksDecoding.pUserData = nullptr;
allocationCallbacksDecoding.onMalloc = Unreal_Malloc;
allocationCallbacksDecoding.onRealloc = Unreal_Realloc;
allocationCallbacksDecoding.onFree = Unreal_Free;
drmp3 mp3;
if (!drmp3_init_memory(&mp3, AudioDataArray.GetData(), AudioDataArray.Num() - 2, &allocationCallbacksDecoding))
{
UE_LOG(LogPreimportedSoundFactory, Log, TEXT("The file you are trying to import is not an MP3 file."));
return nullptr;
}
PreimportedSoundAsset = NewObject<UPreimportedSoundAsset>(InParent, InClass, InName, Flags);
PreimportedSoundAsset->AudioDataArray = AudioDataArray;
PreimportedSoundAsset->SourceFilePath = Filename;
PreimportedSoundAsset->SoundDuration = ConvertSecToFormattedDuration(
static_cast<int32>(drmp3_get_pcm_frame_count(&mp3)) / mp3.sampleRate);
PreimportedSoundAsset->NumberOfChannels = mp3.channels;
PreimportedSoundAsset->SampleRate = mp3.sampleRate;
drmp3_uninit(&mp3);
}
else
{
UE_LOG(LogPreimportedSoundFactory, Log, TEXT("Unable to read the audio file. Check file permissions."));
return nullptr;
}
bOutOperationCanceled = false;
return PreimportedSoundAsset;
}
FString UPreimportedSoundFactory::ConvertSecToFormattedDuration(int32 Seconds)
{
FString FinalString;
const int32 NewHours = Seconds / 3600;
if (NewHours > 0)
{
FinalString += ((NewHours < 10) ? TEXT("0") + FString::FromInt(NewHours) : FString::FromInt(NewHours)) +
TEXT(":");
}
Seconds = Seconds % 3600;
const int32 NewMinutes = Seconds / 60;
if (NewMinutes > 0)
{
FinalString += ((NewMinutes < 10) ? TEXT("0") + FString::FromInt(NewMinutes) : FString::FromInt(NewMinutes)) +
TEXT(":");
}
const int32 NewSeconds = Seconds % 60;
if (NewSeconds > 0)
{
FinalString += (NewSeconds < 10) ? TEXT("0") + FString::FromInt(NewSeconds) : FString::FromInt(NewSeconds);
}
return FinalString;
}
@@ -0,0 +1,17 @@
// Georgy Treshchev 2021.
#include "RuntimeAudioImporterEditor.h"
#define LOCTEXT_NAMESPACE "FRuntimeAudioImporterEditorModule"
void FRuntimeAudioImporterEditorModule::StartupModule()
{
}
void FRuntimeAudioImporterEditorModule::ShutdownModule()
{
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FRuntimeAudioImporterEditorModule, RuntimeAudioImporterEditor)
@@ -0,0 +1,44 @@
// Georgy Treshchev 2021.
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "PreimportedSoundFactory.generated.h"
/** Declaring custom logging */
DECLARE_LOG_CATEGORY_EXTERN(LogPreimportedSoundFactory, Error, All);
/**
* Factory for pre-importing audio files. Only MP3 is available for pre-import because it takes up the least amount of memory.
*/
UCLASS()
class RUNTIMEAUDIOIMPORTEREDITOR_API UPreimportedSoundFactory : public UFactory
{
GENERATED_BODY()
public:
UPreimportedSoundFactory();
/**
* Check whether the audio file can be imported.
*
* @param Filename The name of the importing audio file
* @note Only MP3 is available for pre-import because it takes up the least amount of memory.
*/
virtual bool FactoryCanImport(const FString& Filename) override;
virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags,
const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn,
bool& bOutOperationCanceled) override;
private:
/**
* Convert sound duration from seconds to formatted string (hh:mm:ss)
*
* @param Seconds Audio duration in seconds
*/
static FString ConvertSecToFormattedDuration(int32 Seconds);
};
@@ -0,0 +1,16 @@
// Respirant 2020.
#pragma once
#include "Modules/ModuleManager.h"
class FRuntimeAudioImporterEditorModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
@@ -0,0 +1,46 @@
// Georgy Treshchev 2021.
using UnrealBuildTool;
public class RuntimeAudioImporterEditor : ModuleRules
{
public RuntimeAudioImporterEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDefinitions.AddRange(
new string[] {
"DR_MP3_IMPLEMENTATION=1"
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"RuntimeAudioImporter"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"UnrealEd"
}
);
}
}
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
+14
View File
@@ -0,0 +1,14 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class AudioImporterTestTarget : TargetRules
{
public AudioImporterTestTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V2;
ExtraModuleNames.AddRange( new string[] { "AudioImporterTest" } );
}
}
@@ -0,0 +1,23 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class AudioImporterTest : ModuleRules
{
public AudioImporterTest(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AudioImporterTest.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, AudioImporterTest, "AudioImporterTest" );
@@ -0,0 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
@@ -0,0 +1,5 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AudioImporterTestGameModeBase.h"
@@ -0,0 +1,17 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "AudioImporterTestGameModeBase.generated.h"
/**
*
*/
UCLASS()
class AUDIOIMPORTERTEST_API AAudioImporterTestGameModeBase : public AGameModeBase
{
GENERATED_BODY()
};
+14
View File
@@ -0,0 +1,14 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;
public class AudioImporterTestEditorTarget : TargetRules
{
public AudioImporterTestEditorTarget( TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V2;
ExtraModuleNames.AddRange( new string[] { "AudioImporterTest" } );
}
}